turbopack_ecmascript/
lib.rs

1// Needed for swc visit_ macros
2#![allow(non_local_definitions)]
3#![feature(box_patterns)]
4#![feature(min_specialization)]
5#![feature(iter_intersperse)]
6#![feature(int_roundings)]
7#![feature(arbitrary_self_types)]
8#![feature(arbitrary_self_types_pointers)]
9#![recursion_limit = "256"]
10
11pub mod analyzer;
12pub mod annotations;
13pub mod async_chunk;
14pub mod chunk;
15pub mod code_gen;
16mod errors;
17pub mod inlined_bytes_module;
18pub mod magic_identifier;
19pub mod manifest;
20mod merged_module;
21pub mod minify;
22pub mod parse;
23mod path_visitor;
24pub mod references;
25pub mod runtime_functions;
26pub mod side_effect_optimization;
27pub mod source_map;
28pub(crate) mod static_code;
29mod swc_comments;
30pub mod text;
31pub(crate) mod transform;
32pub mod tree_shake;
33pub mod typescript;
34pub mod utils;
35pub mod webpack;
36pub mod worker_chunk;
37
38use std::{
39    borrow::Cow,
40    collections::hash_map::Entry,
41    fmt::{Debug, Display, Formatter},
42    mem::take,
43    sync::{Arc, Mutex},
44};
45
46use anyhow::{Context, Result, anyhow, bail};
47use chunk::EcmascriptChunkItem;
48use code_gen::{CodeGeneration, CodeGenerationHoistedStmt};
49use either::Either;
50use itertools::Itertools;
51use parse::{ParseResult, parse};
52use path_visitor::ApplyVisitors;
53use references::esm::UrlRewriteBehavior;
54pub use references::{AnalyzeEcmascriptModuleResult, TURBOPACK_HELPER};
55use rustc_hash::{FxHashMap, FxHashSet};
56use serde::{Deserialize, Serialize};
57use smallvec::SmallVec;
58pub use static_code::StaticEcmascriptCode;
59use swc_core::{
60    atoms::Atom,
61    base::SwcComments,
62    common::{
63        BytePos, DUMMY_SP, FileName, GLOBALS, Globals, Loc, Mark, SourceFile, SourceMap,
64        SourceMapper, Span, SpanSnippetError, SyntaxContext,
65        comments::{Comment, CommentKind, Comments},
66        source_map::{FileLinesResult, Files, SourceMapLookupError},
67        util::take::Take,
68    },
69    ecma::{
70        ast::{
71            self, CallExpr, Callee, Decl, EmptyStmt, Expr, ExprStmt, Id, Ident, ModuleItem,
72            Program, Script, SourceMapperExt, Stmt,
73        },
74        codegen::{Emitter, text_writer::JsWriter},
75        utils::StmtLikeInjector,
76        visit::{VisitMut, VisitMutWith, VisitMutWithAstPath},
77    },
78    quote,
79};
80use tracing::{Instrument, Level, instrument};
81pub use transform::{
82    CustomTransformer, EcmascriptInputTransform, EcmascriptInputTransforms, TransformContext,
83    TransformPlugin,
84};
85use turbo_rcstr::{RcStr, rcstr};
86use turbo_tasks::{
87    FxDashMap, FxIndexMap, IntoTraitRef, NonLocalValue, ReadRef, ResolvedVc, TaskInput,
88    TryFlatJoinIterExt, TryJoinIterExt, Upcast, ValueToString, Vc, trace::TraceRawVcs,
89};
90use turbo_tasks_fs::{FileJsonContent, FileSystemPath, glob::Glob, rope::Rope};
91use turbopack_core::{
92    asset::{Asset, AssetContent},
93    chunk::{
94        AsyncModuleInfo, ChunkItem, ChunkType, ChunkableModule, ChunkingContext, EvaluatableAsset,
95        MergeableModule, MergeableModuleExposure, MergeableModules, MergeableModulesExposed,
96        MinifyType, ModuleChunkItemIdExt, ModuleId,
97    },
98    compile_time_info::CompileTimeInfo,
99    context::AssetContext,
100    ident::AssetIdent,
101    module::{Module, OptionModule},
102    module_graph::ModuleGraph,
103    output::OutputAssetsReference,
104    reference::ModuleReferences,
105    reference_type::InnerAssets,
106    resolve::{
107        FindContextFileResult, find_context_file, origin::ResolveOrigin, package_json,
108        parse::Request,
109    },
110    source::Source,
111    source_map::GenerateSourceMap,
112};
113// TODO remove this
114pub use turbopack_resolve::ecmascript as resolve;
115
116use self::chunk::{EcmascriptChunkItemContent, EcmascriptChunkType, EcmascriptExports};
117use crate::{
118    analyzer::graph::EvalContext,
119    chunk::{EcmascriptChunkPlaceable, placeable::is_marked_as_side_effect_free},
120    code_gen::{CodeGens, ModifiableAst},
121    merged_module::MergedEcmascriptModule,
122    parse::generate_js_source_map,
123    references::{
124        analyze_ecmascript_module,
125        async_module::OptionAsyncModule,
126        esm::{base::EsmAssetReferences, export},
127    },
128    side_effect_optimization::reference::EcmascriptModulePartReference,
129    swc_comments::{CowComments, ImmutableComments},
130    transform::{remove_directives, remove_shebang},
131};
132
133#[derive(
134    Eq,
135    PartialEq,
136    Hash,
137    Debug,
138    Clone,
139    Copy,
140    Default,
141    TaskInput,
142    TraceRawVcs,
143    NonLocalValue,
144    Serialize,
145    Deserialize,
146)]
147pub enum SpecifiedModuleType {
148    #[default]
149    Automatic,
150    CommonJs,
151    EcmaScript,
152}
153
154#[derive(
155    PartialOrd,
156    Ord,
157    PartialEq,
158    Eq,
159    Hash,
160    Debug,
161    Clone,
162    Copy,
163    Default,
164    Serialize,
165    Deserialize,
166    TaskInput,
167    TraceRawVcs,
168    NonLocalValue,
169)]
170#[serde(rename_all = "kebab-case")]
171pub enum TreeShakingMode {
172    ModuleFragments,
173    #[default]
174    ReexportsOnly,
175}
176
177#[derive(
178    PartialOrd,
179    Ord,
180    PartialEq,
181    Eq,
182    Hash,
183    Debug,
184    Clone,
185    Copy,
186    Default,
187    Serialize,
188    Deserialize,
189    TaskInput,
190    TraceRawVcs,
191    NonLocalValue,
192)]
193pub enum AnalyzeMode {
194    /// For bundling only, no tracing of referenced files.
195    #[default]
196    CodeGeneration,
197    /// For bundling and tracing of referenced files.
198    CodeGenerationAndTracing,
199    /// For tracing of referenced files only, no bundling (i.e. no codegen).
200    Tracing,
201}
202
203impl AnalyzeMode {
204    pub fn is_tracing(self) -> bool {
205        match self {
206            AnalyzeMode::Tracing | AnalyzeMode::CodeGenerationAndTracing => true,
207            AnalyzeMode::CodeGeneration => false,
208        }
209    }
210
211    pub fn is_code_gen(self) -> bool {
212        match self {
213            AnalyzeMode::CodeGeneration | AnalyzeMode::CodeGenerationAndTracing => true,
214            AnalyzeMode::Tracing => false,
215        }
216    }
217}
218
219#[turbo_tasks::value(transparent)]
220pub struct OptionTreeShaking(pub Option<TreeShakingMode>);
221
222/// The constant to replace `typeof window` with.
223#[derive(
224    Copy,
225    Clone,
226    PartialEq,
227    Eq,
228    Debug,
229    Hash,
230    Serialize,
231    Deserialize,
232    TraceRawVcs,
233    NonLocalValue,
234    TaskInput,
235)]
236pub enum TypeofWindow {
237    Object,
238    Undefined,
239}
240
241#[turbo_tasks::value(shared)]
242#[derive(Debug, Default, Copy, Clone)]
243pub struct EcmascriptOptions {
244    /// variant of tree shaking to use
245    pub tree_shaking_mode: Option<TreeShakingMode>,
246    /// module is forced to a specific type (happens e. g. for .cjs and .mjs)
247    pub specified_module_type: SpecifiedModuleType,
248    /// Determines how to treat `new URL(...)` rewrites.
249    /// This allows to construct url depends on the different building context,
250    /// e.g. SSR, CSR, or Node.js.
251    pub url_rewrite_behavior: Option<UrlRewriteBehavior>,
252    /// External imports should used `__turbopack_import__` instead of
253    /// `__turbopack_require__` and become async module references.
254    pub import_externals: bool,
255    /// Ignore very dynamic requests which doesn't have any static known part.
256    /// If false, they will reference the whole directory. If true, they won't
257    /// reference anything and lead to an runtime error instead.
258    pub ignore_dynamic_requests: bool,
259    /// If true, it reads a sourceMappingURL comment from the end of the file,
260    /// reads and generates a source map.
261    pub extract_source_map: bool,
262    /// If true, it stores the last successful parse result in state and keeps using it when
263    /// parsing fails. This is useful to keep the module graph structure intact when syntax errors
264    /// are temporarily introduced.
265    pub keep_last_successful_parse: bool,
266    /// Whether the modules in this context are never chunked/codegen-ed, but only used for
267    /// tracing.
268    pub analyze_mode: AnalyzeMode,
269    // TODO this should just be handled via CompileTimeInfo FreeVarReferences, but then it
270    // (currently) wouldn't be possible to have different replacement values in user code vs
271    // node_modules.
272    /// Whether to replace `typeof window` with some constant value.
273    pub enable_typeof_window_inlining: Option<TypeofWindow>,
274    /// Whether to allow accessing exports info via `__webpack_exports_info__`.
275    pub enable_exports_info_inlining: bool,
276
277    pub inline_helpers: bool,
278}
279
280#[turbo_tasks::value]
281#[derive(Hash, Debug, Copy, Clone, TaskInput)]
282pub enum EcmascriptModuleAssetType {
283    /// Module with EcmaScript code
284    Ecmascript,
285    /// Module with (presumed) EcmaScript code, but it was extensionless
286    EcmascriptExtensionless,
287    /// Module with TypeScript code without types
288    Typescript {
289        // parse JSX syntax.
290        tsx: bool,
291        // follow references to imported types.
292        analyze_types: bool,
293    },
294    /// Module with TypeScript declaration code
295    TypescriptDeclaration,
296}
297
298impl Display for EcmascriptModuleAssetType {
299    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
300        match self {
301            EcmascriptModuleAssetType::Ecmascript => write!(f, "ecmascript"),
302            EcmascriptModuleAssetType::EcmascriptExtensionless => {
303                write!(f, "ecmascript extensionless")
304            }
305            EcmascriptModuleAssetType::Typescript { tsx, analyze_types } => {
306                write!(f, "typescript")?;
307                if *tsx {
308                    write!(f, " with JSX")?;
309                }
310                if *analyze_types {
311                    write!(f, " with types")?;
312                }
313                Ok(())
314            }
315            EcmascriptModuleAssetType::TypescriptDeclaration => write!(f, "typescript declaration"),
316        }
317    }
318}
319
320#[derive(Clone)]
321pub struct EcmascriptModuleAssetBuilder {
322    source: ResolvedVc<Box<dyn Source>>,
323    asset_context: ResolvedVc<Box<dyn AssetContext>>,
324    ty: EcmascriptModuleAssetType,
325    transforms: ResolvedVc<EcmascriptInputTransforms>,
326    options: ResolvedVc<EcmascriptOptions>,
327    compile_time_info: ResolvedVc<CompileTimeInfo>,
328    inner_assets: Option<ResolvedVc<InnerAssets>>,
329}
330
331impl EcmascriptModuleAssetBuilder {
332    pub fn with_inner_assets(mut self, inner_assets: ResolvedVc<InnerAssets>) -> Self {
333        self.inner_assets = Some(inner_assets);
334        self
335    }
336
337    pub fn with_type(mut self, ty: EcmascriptModuleAssetType) -> Self {
338        self.ty = ty;
339        self
340    }
341
342    pub fn build(self) -> Vc<EcmascriptModuleAsset> {
343        if let Some(inner_assets) = self.inner_assets {
344            EcmascriptModuleAsset::new_with_inner_assets(
345                *self.source,
346                *self.asset_context,
347                self.ty,
348                *self.transforms,
349                *self.options,
350                *self.compile_time_info,
351                *inner_assets,
352            )
353        } else {
354            EcmascriptModuleAsset::new(
355                *self.source,
356                *self.asset_context,
357                self.ty,
358                *self.transforms,
359                *self.options,
360                *self.compile_time_info,
361            )
362        }
363    }
364}
365
366#[turbo_tasks::value]
367pub struct EcmascriptModuleAsset {
368    pub source: ResolvedVc<Box<dyn Source>>,
369    pub asset_context: ResolvedVc<Box<dyn AssetContext>>,
370    pub ty: EcmascriptModuleAssetType,
371    pub transforms: ResolvedVc<EcmascriptInputTransforms>,
372    pub options: ResolvedVc<EcmascriptOptions>,
373    pub compile_time_info: ResolvedVc<CompileTimeInfo>,
374    pub inner_assets: Option<ResolvedVc<InnerAssets>>,
375    #[turbo_tasks(debug_ignore)]
376    last_successful_parse: turbo_tasks::TransientState<ReadRef<ParseResult>>,
377}
378impl core::fmt::Debug for EcmascriptModuleAsset {
379    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
380        f.debug_struct("EcmascriptModuleAsset")
381            .field("source", &self.source)
382            .field("asset_context", &self.asset_context)
383            .field("ty", &self.ty)
384            .field("transforms", &self.transforms)
385            .field("options", &self.options)
386            .field("compile_time_info", &self.compile_time_info)
387            .field("inner_assets", &self.inner_assets)
388            .finish()
389    }
390}
391
392#[turbo_tasks::value_trait]
393pub trait EcmascriptParsable {
394    #[turbo_tasks::function]
395    fn failsafe_parse(self: Vc<Self>) -> Result<Vc<ParseResult>>;
396
397    #[turbo_tasks::function]
398    fn parse_original(self: Vc<Self>) -> Result<Vc<ParseResult>>;
399
400    #[turbo_tasks::function]
401    fn ty(self: Vc<Self>) -> Result<Vc<EcmascriptModuleAssetType>>;
402}
403
404#[turbo_tasks::value_trait]
405pub trait EcmascriptAnalyzable: Module + Asset {
406    #[turbo_tasks::function]
407    fn analyze(self: Vc<Self>) -> Vc<AnalyzeEcmascriptModuleResult>;
408
409    /// Generates module contents without an analysis pass. This is useful for
410    /// transforming code that is not a module, e.g. runtime code.
411    #[turbo_tasks::function]
412    async fn module_content_without_analysis(
413        self: Vc<Self>,
414        generate_source_map: bool,
415    ) -> Result<Vc<EcmascriptModuleContent>>;
416
417    #[turbo_tasks::function]
418    async fn module_content_options(
419        self: Vc<Self>,
420        chunking_context: Vc<Box<dyn ChunkingContext>>,
421        async_module_info: Option<Vc<AsyncModuleInfo>>,
422    ) -> Result<Vc<EcmascriptModuleContentOptions>>;
423}
424
425pub trait EcmascriptAnalyzableExt {
426    fn module_content(
427        self: Vc<Self>,
428        chunking_context: Vc<Box<dyn ChunkingContext>>,
429        async_module_info: Option<Vc<AsyncModuleInfo>>,
430    ) -> Vc<EcmascriptModuleContent>;
431}
432
433impl<T> EcmascriptAnalyzableExt for T
434where
435    T: EcmascriptAnalyzable + Upcast<Box<dyn EcmascriptAnalyzable>>,
436{
437    fn module_content(
438        self: Vc<Self>,
439        chunking_context: Vc<Box<dyn ChunkingContext>>,
440        async_module_info: Option<Vc<AsyncModuleInfo>>,
441    ) -> Vc<EcmascriptModuleContent> {
442        let analyzable = Vc::upcast_non_strict::<Box<dyn EcmascriptAnalyzable>>(self);
443        let own_options = analyzable.module_content_options(chunking_context, async_module_info);
444        EcmascriptModuleContent::new(own_options)
445    }
446}
447
448impl EcmascriptModuleAsset {
449    pub fn builder(
450        source: ResolvedVc<Box<dyn Source>>,
451        asset_context: ResolvedVc<Box<dyn AssetContext>>,
452        transforms: ResolvedVc<EcmascriptInputTransforms>,
453        options: ResolvedVc<EcmascriptOptions>,
454        compile_time_info: ResolvedVc<CompileTimeInfo>,
455    ) -> EcmascriptModuleAssetBuilder {
456        EcmascriptModuleAssetBuilder {
457            source,
458            asset_context,
459            ty: EcmascriptModuleAssetType::Ecmascript,
460            transforms,
461            options,
462            compile_time_info,
463            inner_assets: None,
464        }
465    }
466}
467
468#[turbo_tasks::value]
469#[derive(Clone)]
470pub(crate) struct ModuleTypeResult {
471    pub module_type: SpecifiedModuleType,
472    pub referenced_package_json: Option<FileSystemPath>,
473}
474
475#[turbo_tasks::value_impl]
476impl ModuleTypeResult {
477    #[turbo_tasks::function]
478    fn new(module_type: SpecifiedModuleType) -> Vc<Self> {
479        Self::cell(ModuleTypeResult {
480            module_type,
481            referenced_package_json: None,
482        })
483    }
484
485    #[turbo_tasks::function]
486    fn new_with_package_json(
487        module_type: SpecifiedModuleType,
488        package_json: FileSystemPath,
489    ) -> Vc<Self> {
490        Self::cell(ModuleTypeResult {
491            module_type,
492            referenced_package_json: Some(package_json),
493        })
494    }
495}
496
497#[turbo_tasks::value_impl]
498impl EcmascriptParsable for EcmascriptModuleAsset {
499    #[turbo_tasks::function]
500    async fn failsafe_parse(self: Vc<Self>) -> Result<Vc<ParseResult>> {
501        let this = self.await?;
502        let real_result = this.parse().await?;
503        if this.options.await?.keep_last_successful_parse {
504            let real_result_value = real_result.await?;
505            let result_value = if matches!(*real_result_value, ParseResult::Ok { .. }) {
506                this.last_successful_parse
507                    .set_unconditionally(real_result_value.clone());
508                real_result_value
509            } else {
510                let state_ref = this.last_successful_parse.get();
511                state_ref.as_ref().unwrap_or(&real_result_value).clone()
512            };
513            Ok(ReadRef::cell(result_value))
514        } else {
515            Ok(real_result)
516        }
517    }
518
519    #[turbo_tasks::function]
520    fn parse_original(self: Vc<Self>) -> Vc<ParseResult> {
521        self.failsafe_parse()
522    }
523
524    #[turbo_tasks::function]
525    fn ty(&self) -> Vc<EcmascriptModuleAssetType> {
526        self.ty.cell()
527    }
528}
529
530#[turbo_tasks::value_impl]
531impl EcmascriptAnalyzable for EcmascriptModuleAsset {
532    #[turbo_tasks::function]
533    fn analyze(self: Vc<Self>) -> Vc<AnalyzeEcmascriptModuleResult> {
534        analyze_ecmascript_module(self, None)
535    }
536
537    /// Generates module contents without an analysis pass. This is useful for
538    /// transforming code that is not a module, e.g. runtime code.
539    #[turbo_tasks::function]
540    async fn module_content_without_analysis(
541        self: Vc<Self>,
542        generate_source_map: bool,
543    ) -> Result<Vc<EcmascriptModuleContent>> {
544        let this = self.await?;
545
546        let parsed = this.parse().await?;
547
548        Ok(EcmascriptModuleContent::new_without_analysis(
549            parsed,
550            self.ident(),
551            this.options.await?.specified_module_type,
552            generate_source_map,
553        ))
554    }
555
556    #[turbo_tasks::function]
557    async fn module_content_options(
558        self: ResolvedVc<Self>,
559        chunking_context: ResolvedVc<Box<dyn ChunkingContext>>,
560        async_module_info: Option<ResolvedVc<AsyncModuleInfo>>,
561    ) -> Result<Vc<EcmascriptModuleContentOptions>> {
562        let parsed = self.await?.parse().await?.to_resolved().await?;
563
564        let analyze = self.analyze();
565        let analyze_ref = analyze.await?;
566
567        let module_type_result = self.determine_module_type().await?;
568        let generate_source_map = *chunking_context
569            .reference_module_source_maps(Vc::upcast(*self))
570            .await?;
571
572        Ok(EcmascriptModuleContentOptions {
573            parsed: Some(parsed),
574            module: ResolvedVc::upcast(self),
575            specified_module_type: module_type_result.module_type,
576            chunking_context,
577            references: analyze.references().to_resolved().await?,
578            esm_references: analyze_ref.esm_references,
579            part_references: vec![],
580            code_generation: analyze_ref.code_generation,
581            async_module: analyze_ref.async_module,
582            generate_source_map,
583            original_source_map: analyze_ref.source_map,
584            exports: analyze_ref.exports,
585            async_module_info,
586        }
587        .cell())
588    }
589}
590
591#[turbo_tasks::function]
592async fn determine_module_type_for_directory(
593    context_path: FileSystemPath,
594) -> Result<Vc<ModuleTypeResult>> {
595    let find_package_json =
596        find_context_file(context_path, package_json().resolve().await?, false).await?;
597    let FindContextFileResult::Found(package_json, _) = &*find_package_json else {
598        return Ok(ModuleTypeResult::new(SpecifiedModuleType::Automatic));
599    };
600
601    // analysis.add_reference(PackageJsonReference::new(package_json));
602    if let FileJsonContent::Content(content) = &*package_json.read_json().await?
603        && let Some(r#type) = content.get("type")
604    {
605        return Ok(ModuleTypeResult::new_with_package_json(
606            match r#type.as_str() {
607                Some("module") => SpecifiedModuleType::EcmaScript,
608                Some("commonjs") => SpecifiedModuleType::CommonJs,
609                _ => SpecifiedModuleType::Automatic,
610            },
611            package_json.clone(),
612        ));
613    }
614
615    Ok(ModuleTypeResult::new_with_package_json(
616        SpecifiedModuleType::Automatic,
617        package_json.clone(),
618    ))
619}
620
621#[turbo_tasks::value_impl]
622impl EcmascriptModuleAsset {
623    #[turbo_tasks::function]
624    pub fn new(
625        source: ResolvedVc<Box<dyn Source>>,
626        asset_context: ResolvedVc<Box<dyn AssetContext>>,
627        ty: EcmascriptModuleAssetType,
628        transforms: ResolvedVc<EcmascriptInputTransforms>,
629        options: ResolvedVc<EcmascriptOptions>,
630        compile_time_info: ResolvedVc<CompileTimeInfo>,
631    ) -> Vc<Self> {
632        Self::cell(EcmascriptModuleAsset {
633            source,
634            asset_context,
635            ty,
636            transforms,
637            options,
638
639            compile_time_info,
640            inner_assets: None,
641            last_successful_parse: Default::default(),
642        })
643    }
644
645    #[turbo_tasks::function]
646    pub async fn new_with_inner_assets(
647        source: ResolvedVc<Box<dyn Source>>,
648        asset_context: ResolvedVc<Box<dyn AssetContext>>,
649        ty: EcmascriptModuleAssetType,
650        transforms: ResolvedVc<EcmascriptInputTransforms>,
651        options: ResolvedVc<EcmascriptOptions>,
652        compile_time_info: ResolvedVc<CompileTimeInfo>,
653        inner_assets: ResolvedVc<InnerAssets>,
654    ) -> Result<Vc<Self>> {
655        if inner_assets.await?.is_empty() {
656            Ok(Self::new(
657                *source,
658                *asset_context,
659                ty,
660                *transforms,
661                *options,
662                *compile_time_info,
663            ))
664        } else {
665            Ok(Self::cell(EcmascriptModuleAsset {
666                source,
667                asset_context,
668                ty,
669                transforms,
670                options,
671                compile_time_info,
672                inner_assets: Some(inner_assets),
673                last_successful_parse: Default::default(),
674            }))
675        }
676    }
677
678    #[turbo_tasks::function]
679    pub fn source(&self) -> Vc<Box<dyn Source>> {
680        *self.source
681    }
682
683    #[turbo_tasks::function]
684    pub fn analyze(self: Vc<Self>) -> Vc<AnalyzeEcmascriptModuleResult> {
685        analyze_ecmascript_module(self, None)
686    }
687
688    #[turbo_tasks::function]
689    pub fn options(&self) -> Vc<EcmascriptOptions> {
690        *self.options
691    }
692}
693
694impl EcmascriptModuleAsset {
695    pub async fn parse(&self) -> Result<Vc<ParseResult>> {
696        let options = self.options.await?;
697        Ok(parse(
698            *self.source,
699            self.ty,
700            *self.transforms,
701            options.analyze_mode == AnalyzeMode::Tracing,
702            options.inline_helpers,
703        ))
704    }
705
706    #[tracing::instrument(level = "trace", skip_all)]
707    pub(crate) async fn determine_module_type(self: Vc<Self>) -> Result<ReadRef<ModuleTypeResult>> {
708        let this = self.await?;
709
710        match this.options.await?.specified_module_type {
711            SpecifiedModuleType::EcmaScript => {
712                return ModuleTypeResult::new(SpecifiedModuleType::EcmaScript).await;
713            }
714            SpecifiedModuleType::CommonJs => {
715                return ModuleTypeResult::new(SpecifiedModuleType::CommonJs).await;
716            }
717            SpecifiedModuleType::Automatic => {}
718        }
719
720        determine_module_type_for_directory(self.origin_path().await?.parent()).await
721    }
722}
723
724#[turbo_tasks::value_impl]
725impl Module for EcmascriptModuleAsset {
726    #[turbo_tasks::function]
727    async fn ident(&self) -> Result<Vc<AssetIdent>> {
728        let mut ident = self.source.ident().owned().await?;
729        if let Some(inner_assets) = self.inner_assets {
730            for (name, asset) in inner_assets.await?.iter() {
731                ident.add_asset(name.clone(), asset.ident().to_resolved().await?);
732            }
733        }
734        ident.add_modifier(rcstr!("ecmascript"));
735        ident.layer = Some(self.asset_context.into_trait_ref().await?.layer());
736        Ok(AssetIdent::new(ident))
737    }
738
739    #[turbo_tasks::function]
740    fn references(self: Vc<Self>) -> Result<Vc<ModuleReferences>> {
741        Ok(self.analyze().references())
742    }
743
744    #[turbo_tasks::function]
745    async fn is_self_async(self: Vc<Self>) -> Result<Vc<bool>> {
746        if let Some(async_module) = *self.get_async_module().await? {
747            Ok(async_module.is_self_async(self.references()))
748        } else {
749            Ok(Vc::cell(false))
750        }
751    }
752
753    #[turbo_tasks::function]
754    async fn is_marked_as_side_effect_free(
755        self: Vc<Self>,
756        side_effect_free_packages: Vc<Glob>,
757    ) -> Result<Vc<bool>> {
758        // Check package.json first, so that we can skip parsing the module if it's marked that way.
759        let pkg_side_effect_free = is_marked_as_side_effect_free(
760            self.ident().path().owned().await?,
761            side_effect_free_packages,
762        );
763        Ok(if *pkg_side_effect_free.await? {
764            pkg_side_effect_free
765        } else {
766            Vc::cell(self.analyze().await?.has_side_effect_free_directive)
767        })
768    }
769}
770
771#[turbo_tasks::value_impl]
772impl Asset for EcmascriptModuleAsset {
773    #[turbo_tasks::function]
774    fn content(&self) -> Vc<AssetContent> {
775        self.source.content()
776    }
777}
778
779#[turbo_tasks::value_impl]
780impl ChunkableModule for EcmascriptModuleAsset {
781    #[turbo_tasks::function]
782    async fn as_chunk_item(
783        self: ResolvedVc<Self>,
784        _module_graph: ResolvedVc<ModuleGraph>,
785        chunking_context: ResolvedVc<Box<dyn ChunkingContext>>,
786    ) -> Vc<Box<dyn ChunkItem>> {
787        Vc::upcast(ModuleChunkItem::cell(ModuleChunkItem {
788            module: self,
789            chunking_context,
790        }))
791    }
792}
793
794#[turbo_tasks::value_impl]
795impl EcmascriptChunkPlaceable for EcmascriptModuleAsset {
796    #[turbo_tasks::function]
797    async fn get_exports(self: Vc<Self>) -> Result<Vc<EcmascriptExports>> {
798        Ok(*self.analyze().await?.exports)
799    }
800
801    #[turbo_tasks::function]
802    async fn get_async_module(self: Vc<Self>) -> Result<Vc<OptionAsyncModule>> {
803        Ok(*self.analyze().await?.async_module)
804    }
805}
806
807#[turbo_tasks::value_impl]
808impl MergeableModule for EcmascriptModuleAsset {
809    #[turbo_tasks::function]
810    async fn is_mergeable(self: ResolvedVc<Self>) -> Result<Vc<bool>> {
811        if matches!(
812            &*self.get_exports().await?,
813            EcmascriptExports::EsmExports(_)
814        ) {
815            return Ok(Vc::cell(true));
816        }
817
818        Ok(Vc::cell(false))
819    }
820
821    #[turbo_tasks::function]
822    async fn merge(
823        self: Vc<Self>,
824        modules: Vc<MergeableModulesExposed>,
825        entry_points: Vc<MergeableModules>,
826    ) -> Result<Vc<Box<dyn ChunkableModule>>> {
827        Ok(Vc::upcast(
828            *MergedEcmascriptModule::new(
829                modules,
830                entry_points,
831                self.options().to_resolved().await?,
832            )
833            .await?,
834        ))
835    }
836}
837
838#[turbo_tasks::value_impl]
839impl EvaluatableAsset for EcmascriptModuleAsset {}
840
841#[turbo_tasks::value_impl]
842impl ResolveOrigin for EcmascriptModuleAsset {
843    #[turbo_tasks::function]
844    fn origin_path(&self) -> Vc<FileSystemPath> {
845        self.source.ident().path()
846    }
847
848    #[turbo_tasks::function]
849    fn asset_context(&self) -> Vc<Box<dyn AssetContext>> {
850        *self.asset_context
851    }
852
853    #[turbo_tasks::function]
854    async fn get_inner_asset(&self, request: Vc<Request>) -> Result<Vc<OptionModule>> {
855        Ok(Vc::cell(if let Some(inner_assets) = &self.inner_assets {
856            if let Some(request) = request.await?.request() {
857                inner_assets.await?.get(&request).copied()
858            } else {
859                None
860            }
861        } else {
862            None
863        }))
864    }
865}
866
867#[turbo_tasks::value]
868struct ModuleChunkItem {
869    module: ResolvedVc<EcmascriptModuleAsset>,
870    chunking_context: ResolvedVc<Box<dyn ChunkingContext>>,
871}
872
873#[turbo_tasks::value_impl]
874impl OutputAssetsReference for ModuleChunkItem {}
875
876#[turbo_tasks::value_impl]
877impl ChunkItem for ModuleChunkItem {
878    #[turbo_tasks::function]
879    fn asset_ident(&self) -> Vc<AssetIdent> {
880        self.module.ident()
881    }
882
883    #[turbo_tasks::function]
884    fn chunking_context(&self) -> Vc<Box<dyn ChunkingContext>> {
885        *self.chunking_context
886    }
887
888    #[turbo_tasks::function]
889    async fn ty(&self) -> Result<Vc<Box<dyn ChunkType>>> {
890        Ok(Vc::upcast(
891            Vc::<EcmascriptChunkType>::default().resolve().await?,
892        ))
893    }
894
895    #[turbo_tasks::function]
896    fn module(&self) -> Vc<Box<dyn Module>> {
897        *ResolvedVc::upcast(self.module)
898    }
899}
900
901#[turbo_tasks::value_impl]
902impl EcmascriptChunkItem for ModuleChunkItem {
903    #[turbo_tasks::function]
904    fn content(self: Vc<Self>) -> Vc<EcmascriptChunkItemContent> {
905        panic!("content() should not be called");
906    }
907
908    #[turbo_tasks::function]
909    async fn content_with_async_module_info(
910        self: Vc<Self>,
911        async_module_info: Option<Vc<AsyncModuleInfo>>,
912        _estimated: bool,
913    ) -> Result<Vc<EcmascriptChunkItemContent>> {
914        let span = tracing::info_span!(
915            "code generation",
916            name = display(self.asset_ident().to_string().await?)
917        );
918        async {
919            let this = self.await?;
920            let async_module_options = this
921                .module
922                .get_async_module()
923                .module_options(async_module_info);
924
925            // TODO check if we need to pass async_module_info at all
926            let content = this
927                .module
928                .module_content(*this.chunking_context, async_module_info);
929
930            EcmascriptChunkItemContent::new(content, *this.chunking_context, async_module_options)
931                .resolve()
932                .await
933        }
934        .instrument(span)
935        .await
936    }
937}
938
939/// The transformed contents of an Ecmascript module.
940#[turbo_tasks::value(shared)]
941pub struct EcmascriptModuleContent {
942    pub inner_code: Rope,
943    pub source_map: Option<Rope>,
944    pub is_esm: bool,
945    pub strict: bool,
946    pub additional_ids: SmallVec<[ResolvedVc<ModuleId>; 1]>,
947}
948
949#[turbo_tasks::value(shared)]
950#[derive(Clone, Debug, Hash, TaskInput)]
951pub struct EcmascriptModuleContentOptions {
952    module: ResolvedVc<Box<dyn EcmascriptChunkPlaceable>>,
953    parsed: Option<ResolvedVc<ParseResult>>,
954    specified_module_type: SpecifiedModuleType,
955    chunking_context: ResolvedVc<Box<dyn ChunkingContext>>,
956    references: ResolvedVc<ModuleReferences>,
957    part_references: Vec<ResolvedVc<EcmascriptModulePartReference>>,
958    esm_references: ResolvedVc<EsmAssetReferences>,
959    code_generation: ResolvedVc<CodeGens>,
960    async_module: ResolvedVc<OptionAsyncModule>,
961    generate_source_map: bool,
962    original_source_map: Option<ResolvedVc<Box<dyn GenerateSourceMap>>>,
963    exports: ResolvedVc<EcmascriptExports>,
964    async_module_info: Option<ResolvedVc<AsyncModuleInfo>>,
965}
966
967impl EcmascriptModuleContentOptions {
968    async fn merged_code_gens(
969        &self,
970        scope_hoisting_context: ScopeHoistingContext<'_>,
971        eval_context: &EvalContext,
972    ) -> Result<Vec<CodeGeneration>> {
973        // Don't read `parsed` here again, it will cause a recomputation as `process_parse_result`
974        // has consumed the cell already.
975        let EcmascriptModuleContentOptions {
976            module,
977            chunking_context,
978            references,
979            part_references,
980            esm_references,
981            code_generation,
982            async_module,
983            exports,
984            async_module_info,
985            ..
986        } = self;
987
988        async {
989            let additional_code_gens = [
990                if let Some(async_module) = &*async_module.await? {
991                    Some(
992                        async_module
993                            .code_generation(
994                                async_module_info.map(|info| *info),
995                                **references,
996                                **chunking_context,
997                            )
998                            .await?,
999                    )
1000                } else {
1001                    None
1002                },
1003                if let EcmascriptExports::EsmExports(exports) = *exports.await? {
1004                    Some(
1005                        exports
1006                            .code_generation(
1007                                **chunking_context,
1008                                scope_hoisting_context,
1009                                eval_context,
1010                                *module,
1011                            )
1012                            .await?,
1013                    )
1014                } else {
1015                    None
1016                },
1017            ];
1018
1019            let part_code_gens = part_references
1020                .iter()
1021                .map(|r| r.code_generation(**chunking_context, scope_hoisting_context))
1022                .try_join()
1023                .await?;
1024
1025            let esm_code_gens = esm_references
1026                .await?
1027                .iter()
1028                .map(|r| r.code_generation(**chunking_context, scope_hoisting_context))
1029                .try_join()
1030                .await?;
1031
1032            let code_gens = code_generation
1033                .await?
1034                .iter()
1035                .map(|c| {
1036                    c.code_generation(
1037                        **chunking_context,
1038                        scope_hoisting_context,
1039                        *module,
1040                        *exports,
1041                    )
1042                })
1043                .try_join()
1044                .await?;
1045
1046            anyhow::Ok(
1047                part_code_gens
1048                    .into_iter()
1049                    .chain(esm_code_gens.into_iter())
1050                    .chain(additional_code_gens.into_iter().flatten())
1051                    .chain(code_gens.into_iter())
1052                    .collect(),
1053            )
1054        }
1055        .instrument(tracing::info_span!("precompute code generation"))
1056        .await
1057    }
1058}
1059
1060#[turbo_tasks::value_impl]
1061impl EcmascriptModuleContent {
1062    /// Creates a new [`Vc<EcmascriptModuleContent>`].
1063    #[turbo_tasks::function]
1064    pub async fn new(input: Vc<EcmascriptModuleContentOptions>) -> Result<Vc<Self>> {
1065        let input = input.await?;
1066        let EcmascriptModuleContentOptions {
1067            parsed,
1068            module,
1069            specified_module_type,
1070            generate_source_map,
1071            original_source_map,
1072            chunking_context,
1073            ..
1074        } = &*input;
1075
1076        let minify = chunking_context.minify_type().await?;
1077
1078        let content = process_parse_result(
1079            *parsed,
1080            module.ident(),
1081            *specified_module_type,
1082            *generate_source_map,
1083            *original_source_map,
1084            *minify,
1085            Some(&*input),
1086            None,
1087        )
1088        .await?;
1089        emit_content(content, Default::default()).await
1090    }
1091
1092    /// Creates a new [`Vc<EcmascriptModuleContent>`] without an analysis pass.
1093    #[turbo_tasks::function]
1094    pub async fn new_without_analysis(
1095        parsed: Vc<ParseResult>,
1096        ident: Vc<AssetIdent>,
1097        specified_module_type: SpecifiedModuleType,
1098        generate_source_map: bool,
1099    ) -> Result<Vc<Self>> {
1100        let content = process_parse_result(
1101            Some(parsed.to_resolved().await?),
1102            ident,
1103            specified_module_type,
1104            generate_source_map,
1105            None,
1106            MinifyType::NoMinify,
1107            None,
1108            None,
1109        )
1110        .await?;
1111        emit_content(content, Default::default()).await
1112    }
1113
1114    /// Creates a new [`Vc<EcmascriptModuleContent>`] from multiple modules, performing scope
1115    /// hoisting.
1116    /// - The `modules` argument is a list of all modules to be merged (and whether their exports
1117    ///   should be exposed).
1118    /// - The `entries` argument is a list of modules that should be treated as entry points for the
1119    ///   merged module (used to determine execution order).
1120    #[turbo_tasks::function]
1121    pub async fn new_merged(
1122        modules: Vec<(
1123            ResolvedVc<Box<dyn EcmascriptAnalyzable>>,
1124            MergeableModuleExposure,
1125        )>,
1126        module_options: Vec<Vc<EcmascriptModuleContentOptions>>,
1127        entry_points: Vec<ResolvedVc<Box<dyn EcmascriptAnalyzable>>>,
1128    ) -> Result<Vc<Self>> {
1129        async {
1130            let modules = modules
1131                .into_iter()
1132                .map(|(m, exposed)| {
1133                    (
1134                        ResolvedVc::try_sidecast::<Box<dyn EcmascriptChunkPlaceable>>(m).unwrap(),
1135                        exposed,
1136                    )
1137                })
1138                .collect::<FxIndexMap<_, _>>();
1139            let entry_points = entry_points
1140                .into_iter()
1141                .map(|m| {
1142                    let m =
1143                        ResolvedVc::try_sidecast::<Box<dyn EcmascriptChunkPlaceable>>(m).unwrap();
1144                    (m, modules.get_index_of(&m).unwrap())
1145                })
1146                .collect::<Vec<_>>();
1147
1148            let globals_merged = Globals::default();
1149
1150            let contents = module_options
1151                .iter()
1152                .map(async |options| {
1153                    let options = options.await?;
1154                    let EcmascriptModuleContentOptions {
1155                        chunking_context,
1156                        parsed,
1157                        module,
1158                        specified_module_type,
1159                        generate_source_map,
1160                        original_source_map,
1161                        ..
1162                    } = &*options;
1163
1164                    let result = process_parse_result(
1165                        *parsed,
1166                        module.ident(),
1167                        *specified_module_type,
1168                        *generate_source_map,
1169                        *original_source_map,
1170                        *chunking_context.minify_type().await?,
1171                        Some(&*options),
1172                        Some(ScopeHoistingOptions {
1173                            module: *module,
1174                            modules: &modules,
1175                        }),
1176                    )
1177                    .await?;
1178
1179                    Ok((*module, result))
1180                })
1181                .try_join()
1182                .await?;
1183
1184            let (merged_ast, comments, source_maps, original_source_maps, lookup_table) =
1185                merge_modules(contents, &entry_points, &globals_merged).await?;
1186
1187            // Use the options from an arbitrary module, since they should all be the same with
1188            // regards to minify_type and chunking_context.
1189            let options = module_options.last().unwrap().await?;
1190
1191            let modules_header_width = modules.len().next_power_of_two().trailing_zeros();
1192            let content = CodeGenResult {
1193                program: merged_ast,
1194                source_map: CodeGenResultSourceMap::ScopeHoisting {
1195                    modules_header_width,
1196                    lookup_table: lookup_table.clone(),
1197                    source_maps,
1198                },
1199                comments: CodeGenResultComments::ScopeHoisting {
1200                    modules_header_width,
1201                    lookup_table,
1202                    comments,
1203                },
1204                is_esm: true,
1205                strict: true,
1206                original_source_map: CodeGenResultOriginalSourceMap::ScopeHoisting(
1207                    original_source_maps,
1208                ),
1209                minify: *options.chunking_context.minify_type().await?,
1210                scope_hoisting_syntax_contexts: None,
1211            };
1212
1213            let first_entry = entry_points.first().unwrap().0;
1214            let additional_ids = modules
1215                .keys()
1216                // Additionally set this module factory for all modules that are exposed. The whole
1217                // group might be imported via a different entry import in different chunks (we only
1218                // ensure that the modules are in the same order, not that they form a subgraph that
1219                // is always imported from the same root module).
1220                //
1221                // Also skip the first entry, which is the name of the chunk item.
1222                .filter(|m| {
1223                    **m != first_entry
1224                        && *modules.get(*m).unwrap() == MergeableModuleExposure::External
1225                })
1226                .map(|m| m.chunk_item_id(*options.chunking_context).to_resolved())
1227                .try_join()
1228                .await?
1229                .into();
1230
1231            emit_content(content, additional_ids)
1232                .instrument(tracing::info_span!("emit code"))
1233                .await
1234        }
1235        .instrument(tracing::info_span!(
1236            "generate merged code",
1237            modules = module_options.len()
1238        ))
1239        .await
1240    }
1241}
1242
1243/// Merges multiple Ecmascript modules into a single AST, setting the syntax contexts correctly so
1244/// that imports work.
1245///
1246/// In `contents`, each import from another module in the group must have an Ident with
1247/// - a `ctxt` listed in scope_hoisting_syntax_contexts.module_contexts, and
1248/// - `sym` being the name of the import.
1249///
1250/// This is then used to map back to the variable name and context of the exporting module.
1251#[instrument(level = Level::TRACE, skip_all, name = "merge")]
1252#[allow(clippy::type_complexity)]
1253async fn merge_modules(
1254    mut contents: Vec<(ResolvedVc<Box<dyn EcmascriptChunkPlaceable>>, CodeGenResult)>,
1255    entry_points: &Vec<(ResolvedVc<Box<dyn EcmascriptChunkPlaceable>>, usize)>,
1256    globals_merged: &'_ Globals,
1257) -> Result<(
1258    Program,
1259    Vec<CodeGenResultComments>,
1260    Vec<CodeGenResultSourceMap>,
1261    SmallVec<[ResolvedVc<Box<dyn GenerateSourceMap>>; 1]>,
1262    Arc<Mutex<Vec<ModulePosition>>>,
1263)> {
1264    struct SetSyntaxContextVisitor<'a> {
1265        modules_header_width: u32,
1266        current_module: ResolvedVc<Box<dyn EcmascriptChunkPlaceable>>,
1267        current_module_idx: u32,
1268        lookup_table: &'a mut Vec<ModulePosition>,
1269        /// The export syntax contexts in the current AST, which will be mapped to merged_ctxts
1270        reverse_module_contexts:
1271            FxHashMap<SyntaxContext, ResolvedVc<Box<dyn EcmascriptChunkPlaceable>>>,
1272        /// For a given module, the `eval_context.imports.exports`. So for a given export, this
1273        /// allows looking up the corresponding local binding's name and context.
1274        export_contexts:
1275            &'a FxHashMap<ResolvedVc<Box<dyn EcmascriptChunkPlaceable>>, &'a FxHashMap<RcStr, Id>>,
1276        /// A fresh global SyntaxContext for each module-local context, so that we can merge them
1277        /// into a single global AST.
1278        unique_contexts_cache: &'a mut FxHashMap<
1279            (ResolvedVc<Box<dyn EcmascriptChunkPlaceable>>, SyntaxContext),
1280            SyntaxContext,
1281        >,
1282
1283        error: anyhow::Result<()>,
1284    }
1285
1286    impl<'a> SetSyntaxContextVisitor<'a> {
1287        fn get_context_for(
1288            &mut self,
1289            module: ResolvedVc<Box<dyn EcmascriptChunkPlaceable>>,
1290            local_ctxt: SyntaxContext,
1291        ) -> SyntaxContext {
1292            if let Some(&global_ctxt) = self.unique_contexts_cache.get(&(module, local_ctxt)) {
1293                global_ctxt
1294            } else {
1295                let global_ctxt = SyntaxContext::empty().apply_mark(Mark::new());
1296                self.unique_contexts_cache
1297                    .insert((module, local_ctxt), global_ctxt);
1298                global_ctxt
1299            }
1300        }
1301    }
1302
1303    impl VisitMut for SetSyntaxContextVisitor<'_> {
1304        fn visit_mut_ident(&mut self, ident: &mut Ident) {
1305            let Ident {
1306                sym, ctxt, span, ..
1307            } = ident;
1308
1309            // If this ident is an imported binding, rewrite the name and context to the
1310            // corresponding export in the module that exports it.
1311            if let Some(&module) = self.reverse_module_contexts.get(ctxt) {
1312                let eval_context_exports = self.export_contexts.get(&module).unwrap();
1313                // TODO looking up an Atom in a Map<RcStr, _>
1314                let sym_rc_str: RcStr = sym.as_str().into();
1315                let (local, local_ctxt) = if let Some((local, local_ctxt)) =
1316                    eval_context_exports.get(&sym_rc_str)
1317                {
1318                    (Some(local), *local_ctxt)
1319                } else if sym.starts_with("__TURBOPACK__imported__module__") {
1320                    // The variable corresponding to the `export * as foo from "...";` is generated
1321                    // in the module generating the reexport (and it's not listed in the
1322                    // eval_context). `EsmAssetReference::code_gen` uses a dummy span when
1323                    // generating this variable.
1324                    (None, SyntaxContext::empty())
1325                } else {
1326                    panic!(
1327                        "Expected to find a local export for {sym} with ctxt {ctxt:#?} in \
1328                         {eval_context_exports:?}",
1329                    );
1330                };
1331
1332                let global_ctxt = self.get_context_for(module, local_ctxt);
1333
1334                if let Some(local) = local {
1335                    *sym = local.clone();
1336                }
1337                *ctxt = global_ctxt;
1338                span.visit_mut_with(self);
1339            } else {
1340                ident.visit_mut_children_with(self);
1341            }
1342        }
1343
1344        fn visit_mut_syntax_context(&mut self, local_ctxt: &mut SyntaxContext) {
1345            // The modules have their own local syntax contexts, which needs to be mapped to
1346            // contexts that were actually created in the merged Globals.
1347            let module = self
1348                .reverse_module_contexts
1349                .get(local_ctxt)
1350                .copied()
1351                .unwrap_or(self.current_module);
1352
1353            let global_ctxt = self.get_context_for(module, *local_ctxt);
1354            *local_ctxt = global_ctxt;
1355        }
1356        fn visit_mut_span(&mut self, span: &mut Span) {
1357            // Encode the module index into the span, to be able to retrieve the module later for
1358            // finding the correct Comments and SourceMap.
1359            span.lo = CodeGenResultComments::encode_bytepos_with_vec(
1360                self.modules_header_width,
1361                self.current_module_idx,
1362                span.lo,
1363                self.lookup_table,
1364            )
1365            .unwrap_or_else(|err| {
1366                self.error = Err(err);
1367                span.lo
1368            });
1369            span.hi = CodeGenResultComments::encode_bytepos_with_vec(
1370                self.modules_header_width,
1371                self.current_module_idx,
1372                span.hi,
1373                self.lookup_table,
1374            )
1375            .unwrap_or_else(|err| {
1376                self.error = Err(err);
1377                span.hi
1378            });
1379        }
1380    }
1381
1382    // Extract programs into a separate mutable list so that `content` doesn't have to be mutably
1383    // borrowed (and `export_contexts` doesn't have to clone).
1384    let mut programs = contents
1385        .iter_mut()
1386        .map(|(_, content)| content.program.take())
1387        .collect::<Vec<_>>();
1388
1389    let export_contexts = contents
1390        .iter()
1391        .map(|(module, content)| {
1392            Ok((
1393                *module,
1394                content
1395                    .scope_hoisting_syntax_contexts
1396                    .as_ref()
1397                    .map(|(_, export_contexts)| export_contexts)
1398                    .context("expected exports contexts")?,
1399            ))
1400        })
1401        .collect::<Result<FxHashMap<_, _>>>()?;
1402
1403    let mut lookup_table = Vec::new();
1404    let result = GLOBALS.set(globals_merged, || {
1405        let _ = tracing::trace_span!("merge inner").entered();
1406        // As an optimization, assume an average number of 5 contexts per module.
1407        let mut unique_contexts_cache =
1408            FxHashMap::with_capacity_and_hasher(contents.len() * 5, Default::default());
1409
1410        let mut prepare_module =
1411            |module_count: usize,
1412             current_module_idx: usize,
1413             (module, content): &(ResolvedVc<Box<dyn EcmascriptChunkPlaceable>>, CodeGenResult),
1414             program: &mut Program,
1415             lookup_table: &mut Vec<ModulePosition>| {
1416                let _ = tracing::trace_span!("prepare module").entered();
1417                if let CodeGenResult {
1418                    scope_hoisting_syntax_contexts: Some((module_contexts, _)),
1419                    ..
1420                } = content
1421                {
1422                    let modules_header_width = module_count.next_power_of_two().trailing_zeros();
1423                    GLOBALS.set(globals_merged, || {
1424                        let mut visitor = SetSyntaxContextVisitor {
1425                            modules_header_width,
1426                            current_module: *module,
1427                            current_module_idx: current_module_idx as u32,
1428                            lookup_table,
1429                            reverse_module_contexts: module_contexts
1430                                .iter()
1431                                .map(|e| (*e.value(), *e.key()))
1432                                .collect(),
1433                            export_contexts: &export_contexts,
1434                            unique_contexts_cache: &mut unique_contexts_cache,
1435                            error: Ok(()),
1436                        };
1437                        program.visit_mut_with(&mut visitor);
1438                        visitor.error
1439                    })?;
1440
1441                    Ok(match program.take() {
1442                        Program::Module(module) => Either::Left(module.body.into_iter()),
1443                        // A module without any ModuleItem::ModuleDecl but a
1444                        // SpecifiedModuleType::EcmaScript can still contain a Module::Script.
1445                        Program::Script(script) => {
1446                            Either::Right(script.body.into_iter().map(ModuleItem::Stmt))
1447                        }
1448                    })
1449                } else {
1450                    bail!("Expected scope_hosting_syntax_contexts");
1451                }
1452            };
1453
1454        let mut inserted = FxHashSet::with_capacity_and_hasher(contents.len(), Default::default());
1455        // Start with inserting the entry points, and recursively inline all their imports.
1456        inserted.extend(entry_points.iter().map(|(_, i)| *i));
1457
1458        let mut inserted_imports = FxHashMap::default();
1459
1460        let span = tracing::trace_span!("merge ASTs");
1461        // Replace inserted `__turbopack_merged_esm__(i);` statements with the corresponding
1462        // ith-module.
1463        let mut queue = entry_points
1464            .iter()
1465            .map(|&(_, i)| {
1466                prepare_module(
1467                    contents.len(),
1468                    i,
1469                    &contents[i],
1470                    &mut programs[i],
1471                    &mut lookup_table,
1472                )
1473                .map_err(|err| (i, err))
1474            })
1475            .flatten_ok()
1476            .rev()
1477            .collect::<Result<Vec<_>, _>>()?;
1478        let mut result = vec![];
1479        while let Some(item) = queue.pop() {
1480            if let ModuleItem::Stmt(stmt) = &item {
1481                match stmt {
1482                    Stmt::Expr(ExprStmt { expr, .. }) => {
1483                        if let Expr::Call(CallExpr {
1484                            callee: Callee::Expr(callee),
1485                            args,
1486                            ..
1487                        }) = &**expr
1488                            && callee.is_ident_ref_to("__turbopack_merged_esm__")
1489                        {
1490                            let index =
1491                                args[0].expr.as_lit().unwrap().as_num().unwrap().value as usize;
1492
1493                            // Only insert once, otherwise the module was already executed
1494                            if inserted.insert(index) {
1495                                queue.extend(
1496                                    prepare_module(
1497                                        contents.len(),
1498                                        index,
1499                                        &contents[index],
1500                                        &mut programs[index],
1501                                        &mut lookup_table,
1502                                    )
1503                                    .map_err(|err| (index, err))?
1504                                    .into_iter()
1505                                    .rev(),
1506                                );
1507                            }
1508                            continue;
1509                        }
1510                    }
1511                    Stmt::Decl(Decl::Var(var)) => {
1512                        if let [decl] = &*var.decls
1513                            && let Some(name) = decl.name.as_ident()
1514                            && name.sym.starts_with("__TURBOPACK__imported__module__")
1515                        {
1516                            // var __TURBOPACK__imported__module__.. = __turbopack_context__.i(..);
1517
1518                            // Even if these imports are not side-effect free, they only execute
1519                            // once, so no need to insert multiple times.
1520                            match inserted_imports.entry(name.sym.clone()) {
1521                                Entry::Occupied(entry) => {
1522                                    // If the import was already inserted, we can skip it. The
1523                                    // variable mapping minifies better but is unfortunately
1524                                    // necessary as the syntax contexts of the two imports are
1525                                    // different.
1526                                    let entry_ctxt = *entry.get();
1527                                    let new = Ident::new(name.sym.clone(), DUMMY_SP, name.ctxt);
1528                                    let old = Ident::new(name.sym.clone(), DUMMY_SP, entry_ctxt);
1529                                    result.push(ModuleItem::Stmt(
1530                                        quote!("var $new = $old;" as Stmt,
1531                                            new: Ident = new,
1532                                            old: Ident = old
1533                                        ),
1534                                    ));
1535                                    continue;
1536                                }
1537                                Entry::Vacant(entry) => {
1538                                    entry.insert(name.ctxt);
1539                                }
1540                            }
1541                        }
1542                    }
1543                    _ => (),
1544                }
1545            }
1546
1547            result.push(item);
1548        }
1549        drop(span);
1550
1551        let span = tracing::trace_span!("hygiene").entered();
1552        let mut merged_ast = Program::Module(swc_core::ecma::ast::Module {
1553            body: result,
1554            span: DUMMY_SP,
1555            shebang: None,
1556        });
1557        merged_ast.visit_mut_with(&mut swc_core::ecma::transforms::base::hygiene::hygiene());
1558        drop(span);
1559
1560        Ok((merged_ast, inserted))
1561    });
1562
1563    let (merged_ast, inserted) = match result {
1564        Ok(v) => v,
1565        Err((content_idx, err)) => {
1566            return Err(err.context(format!(
1567                "Processing {}",
1568                contents[content_idx].0.ident().to_string().await?
1569            )));
1570        }
1571    };
1572
1573    debug_assert!(
1574        inserted.len() == contents.len(),
1575        "Not all merged modules were inserted: {:?}",
1576        contents
1577            .iter()
1578            .enumerate()
1579            .map(async |(i, m)| Ok((inserted.contains(&i), m.0.ident().to_string().await?)))
1580            .try_join()
1581            .await?,
1582    );
1583
1584    let comments = contents
1585        .iter_mut()
1586        .map(|(_, content)| content.comments.take())
1587        .collect::<Vec<_>>();
1588
1589    let source_maps = contents
1590        .iter_mut()
1591        .map(|(_, content)| std::mem::take(&mut content.source_map))
1592        .collect::<Vec<_>>();
1593
1594    let original_source_maps = contents
1595        .iter_mut()
1596        .flat_map(|(_, content)| match content.original_source_map {
1597            CodeGenResultOriginalSourceMap::ScopeHoisting(_) => unreachable!(
1598                "Didn't expect nested CodeGenResultOriginalSourceMap::ScopeHoisting: {:?}",
1599                content.original_source_map
1600            ),
1601            CodeGenResultOriginalSourceMap::Single(map) => map,
1602        })
1603        .collect();
1604
1605    Ok((
1606        merged_ast,
1607        comments,
1608        source_maps,
1609        original_source_maps,
1610        Arc::new(Mutex::new(lookup_table)),
1611    ))
1612}
1613
1614/// Provides information about the other modules in the current scope hoisting group.
1615///
1616/// Note that this object contains interior mutability to lazily create syntax contexts in
1617/// `get_module_syntax_context`.
1618#[derive(Clone, Copy)]
1619pub enum ScopeHoistingContext<'a> {
1620    Some {
1621        /// The current module when scope hoisting
1622        module: ResolvedVc<Box<dyn EcmascriptChunkPlaceable>>,
1623        /// All modules in the current group, and whether they should expose their exports
1624        modules:
1625            &'a FxIndexMap<ResolvedVc<Box<dyn EcmascriptChunkPlaceable>>, MergeableModuleExposure>,
1626
1627        is_import_mark: Mark,
1628        globals: &'a Arc<Globals>,
1629        // Interior mutability!
1630        module_syntax_contexts_cache:
1631            &'a FxDashMap<ResolvedVc<Box<dyn EcmascriptChunkPlaceable>>, SyntaxContext>,
1632    },
1633    None,
1634}
1635
1636impl<'a> ScopeHoistingContext<'a> {
1637    /// The current module when scope hoisting
1638    pub fn module(&self) -> Option<ResolvedVc<Box<dyn EcmascriptChunkPlaceable>>> {
1639        match self {
1640            ScopeHoistingContext::Some { module, .. } => Some(*module),
1641            ScopeHoistingContext::None => None,
1642        }
1643    }
1644
1645    /// Whether the current module should not expose it's exports into the module cache.
1646    pub fn skip_module_exports(&self) -> bool {
1647        match self {
1648            ScopeHoistingContext::Some {
1649                module, modules, ..
1650            } => match modules.get(module).unwrap() {
1651                MergeableModuleExposure::None => true,
1652                MergeableModuleExposure::Internal | MergeableModuleExposure::External => false,
1653            },
1654            ScopeHoistingContext::None => false,
1655        }
1656    }
1657
1658    /// To import a specifier from another module, apply this context to the Ident
1659    pub fn get_module_syntax_context(
1660        &self,
1661        module: ResolvedVc<Box<dyn EcmascriptChunkPlaceable>>,
1662    ) -> Option<SyntaxContext> {
1663        match self {
1664            ScopeHoistingContext::Some {
1665                modules,
1666                module_syntax_contexts_cache,
1667                globals,
1668                is_import_mark,
1669                ..
1670            } => {
1671                if !modules.contains_key(&module) {
1672                    return None;
1673                }
1674
1675                Some(match module_syntax_contexts_cache.entry(module) {
1676                    dashmap::Entry::Occupied(e) => *e.get(),
1677                    dashmap::Entry::Vacant(e) => {
1678                        let ctxt = GLOBALS.set(globals, || {
1679                            let mark = Mark::fresh(*is_import_mark);
1680                            SyntaxContext::empty()
1681                                .apply_mark(*is_import_mark)
1682                                .apply_mark(mark)
1683                        });
1684
1685                        e.insert(ctxt);
1686                        ctxt
1687                    }
1688                })
1689            }
1690            ScopeHoistingContext::None => None,
1691        }
1692    }
1693
1694    pub fn get_module_index(
1695        &self,
1696        module: ResolvedVc<Box<dyn EcmascriptChunkPlaceable>>,
1697    ) -> Option<usize> {
1698        match self {
1699            ScopeHoistingContext::Some { modules, .. } => modules.get_index_of(&module),
1700            ScopeHoistingContext::None => None,
1701        }
1702    }
1703}
1704
1705struct CodeGenResult {
1706    program: Program,
1707    source_map: CodeGenResultSourceMap,
1708    comments: CodeGenResultComments,
1709    is_esm: bool,
1710    strict: bool,
1711    original_source_map: CodeGenResultOriginalSourceMap,
1712    minify: MinifyType,
1713    #[allow(clippy::type_complexity)]
1714    /// (Map<Module, corresponding context for imports>, `eval_context.imports.exports`)
1715    scope_hoisting_syntax_contexts: Option<(
1716        FxDashMap<ResolvedVc<Box<dyn EcmascriptChunkPlaceable + 'static>>, SyntaxContext>,
1717        FxHashMap<RcStr, Id>,
1718    )>,
1719}
1720
1721struct ScopeHoistingOptions<'a> {
1722    module: ResolvedVc<Box<dyn EcmascriptChunkPlaceable>>,
1723    modules: &'a FxIndexMap<ResolvedVc<Box<dyn EcmascriptChunkPlaceable>>, MergeableModuleExposure>,
1724}
1725
1726async fn process_parse_result(
1727    parsed: Option<ResolvedVc<ParseResult>>,
1728    ident: Vc<AssetIdent>,
1729    specified_module_type: SpecifiedModuleType,
1730    generate_source_map: bool,
1731    original_source_map: Option<ResolvedVc<Box<dyn GenerateSourceMap>>>,
1732    minify: MinifyType,
1733    options: Option<&EcmascriptModuleContentOptions>,
1734    scope_hoisting_options: Option<ScopeHoistingOptions<'_>>,
1735) -> Result<CodeGenResult> {
1736    with_consumed_parse_result(
1737        parsed,
1738        async |mut program, source_map, globals, eval_context, comments| -> Result<CodeGenResult> {
1739            let (top_level_mark, is_esm, strict) = eval_context
1740                .as_ref()
1741                .map_either(
1742                    |e| {
1743                        (
1744                            e.top_level_mark,
1745                            e.is_esm(specified_module_type),
1746                            e.imports.strict,
1747                        )
1748                    },
1749                    |e| {
1750                        (
1751                            e.top_level_mark,
1752                            e.is_esm(specified_module_type),
1753                            e.imports.strict,
1754                        )
1755                    },
1756                )
1757                .into_inner();
1758
1759            let (mut code_gens, retain_syntax_context, prepend_ident_comment) =
1760                if let Some(scope_hoisting_options) = scope_hoisting_options {
1761                    let is_import_mark = GLOBALS.set(globals, || Mark::new());
1762
1763                    let module_syntax_contexts_cache = FxDashMap::default();
1764                    let ctx = ScopeHoistingContext::Some {
1765                        module: scope_hoisting_options.module,
1766                        modules: scope_hoisting_options.modules,
1767                        module_syntax_contexts_cache: &module_syntax_contexts_cache,
1768                        is_import_mark,
1769                        globals,
1770                    };
1771                    let code_gens = options
1772                        .unwrap()
1773                        .merged_code_gens(
1774                            ctx,
1775                            match &eval_context {
1776                                Either::Left(e) => e,
1777                                Either::Right(e) => e,
1778                            },
1779                        )
1780                        .await?;
1781
1782                    let export_contexts = eval_context
1783                        .map_either(
1784                            |e| Cow::Owned(e.imports.exports),
1785                            |e| Cow::Borrowed(&e.imports.exports),
1786                        )
1787                        .into_inner();
1788                    let preserved_exports =
1789                        match &*scope_hoisting_options.module.get_exports().await? {
1790                            EcmascriptExports::EsmExports(exports) => exports
1791                                .await?
1792                                .exports
1793                                .iter()
1794                                .filter(|(_, e)| matches!(e, export::EsmExport::LocalBinding(_, _)))
1795                                .map(|(name, e)| {
1796                                    if let Some((sym, ctxt)) = export_contexts.get(name) {
1797                                        Ok((sym.clone(), *ctxt))
1798                                    } else {
1799                                        bail!("Couldn't find export {} for binding {:?}", name, e);
1800                                    }
1801                                })
1802                                .collect::<Result<FxHashSet<_>>>()?,
1803                            _ => Default::default(),
1804                        };
1805
1806                    let prepend_ident_comment = if matches!(minify, MinifyType::NoMinify) {
1807                        Some(Comment {
1808                            kind: CommentKind::Line,
1809                            span: DUMMY_SP,
1810                            text: format!(" MERGED MODULE: {}", ident.to_string().await?).into(),
1811                        })
1812                    } else {
1813                        None
1814                    };
1815
1816                    (
1817                        code_gens,
1818                        Some((
1819                            is_import_mark,
1820                            module_syntax_contexts_cache,
1821                            preserved_exports,
1822                            export_contexts,
1823                        )),
1824                        prepend_ident_comment,
1825                    )
1826                } else if let Some(options) = options {
1827                    (
1828                        options
1829                            .merged_code_gens(
1830                                ScopeHoistingContext::None,
1831                                match &eval_context {
1832                                    Either::Left(e) => e,
1833                                    Either::Right(e) => e,
1834                                },
1835                            )
1836                            .await?,
1837                        None,
1838                        None,
1839                    )
1840                } else {
1841                    (vec![], None, None)
1842                };
1843
1844            let extra_comments = SwcComments {
1845                leading: Default::default(),
1846                trailing: Default::default(),
1847            };
1848
1849            process_content_with_code_gens(&mut program, globals, &mut code_gens);
1850
1851            for comments in code_gens.iter_mut().flat_map(|cg| cg.comments.as_mut()) {
1852                let leading = Arc::unwrap_or_clone(take(&mut comments.leading));
1853                let trailing = Arc::unwrap_or_clone(take(&mut comments.trailing));
1854
1855                for (pos, v) in leading {
1856                    extra_comments.leading.entry(pos).or_default().extend(v);
1857                }
1858
1859                for (pos, v) in trailing {
1860                    extra_comments.trailing.entry(pos).or_default().extend(v);
1861                }
1862            }
1863
1864            GLOBALS.set(globals, || {
1865                if let Some(prepend_ident_comment) = prepend_ident_comment {
1866                    let span = Span::dummy_with_cmt();
1867                    extra_comments.add_leading(span.lo, prepend_ident_comment);
1868                    let stmt = Stmt::Empty(EmptyStmt { span });
1869                    match &mut program {
1870                        Program::Module(module) => module.body.prepend_stmt(ModuleItem::Stmt(stmt)),
1871                        Program::Script(script) => script.body.prepend_stmt(stmt),
1872                    }
1873                }
1874
1875                if let Some((is_import_mark, _, preserved_exports, _)) = &retain_syntax_context {
1876                    program.visit_mut_with(&mut hygiene_rename_only(
1877                        Some(top_level_mark),
1878                        *is_import_mark,
1879                        preserved_exports,
1880                    ));
1881                } else {
1882                    program.visit_mut_with(
1883                        &mut swc_core::ecma::transforms::base::hygiene::hygiene_with_config(
1884                            swc_core::ecma::transforms::base::hygiene::Config {
1885                                top_level_mark,
1886                                ..Default::default()
1887                            },
1888                        ),
1889                    );
1890                }
1891                program.visit_mut_with(&mut swc_core::ecma::transforms::base::fixer::fixer(None));
1892
1893                // we need to remove any shebang before bundling as it's only valid as the first
1894                // line in a js file (not in a chunk item wrapped in the runtime)
1895                remove_shebang(&mut program);
1896                remove_directives(&mut program);
1897            });
1898
1899            Ok(CodeGenResult {
1900                program,
1901                source_map: if generate_source_map {
1902                    CodeGenResultSourceMap::Single {
1903                        source_map: source_map.clone(),
1904                    }
1905                } else {
1906                    CodeGenResultSourceMap::None
1907                },
1908                comments: CodeGenResultComments::Single {
1909                    comments,
1910                    extra_comments,
1911                },
1912                is_esm,
1913                strict,
1914                original_source_map: CodeGenResultOriginalSourceMap::Single(original_source_map),
1915                minify,
1916                scope_hoisting_syntax_contexts: retain_syntax_context
1917                    // TODO ideally don't clone here
1918                    .map(|(_, ctxts, _, export_contexts)| (ctxts, export_contexts.into_owned())),
1919            })
1920        },
1921        async |parse_result| -> Result<CodeGenResult> {
1922            Ok(match parse_result {
1923                ParseResult::Ok { .. } => unreachable!(),
1924                ParseResult::Unparsable { messages } => {
1925                    let path = ident.path().to_string().await?;
1926                    let error_messages = messages
1927                        .as_ref()
1928                        .and_then(|m| m.first().map(|f| format!("\n{f}")))
1929                        .unwrap_or("".into());
1930                    let msg = format!("Could not parse module '{path}'\n{error_messages}");
1931                    let body = vec![
1932                        quote!(
1933                            "const e = new Error($msg);" as Stmt,
1934                            msg: Expr = Expr::Lit(msg.into()),
1935                        ),
1936                        quote!("e.code = 'MODULE_UNPARSABLE';" as Stmt),
1937                        quote!("throw e;" as Stmt),
1938                    ];
1939
1940                    CodeGenResult {
1941                        program: Program::Script(Script {
1942                            span: DUMMY_SP,
1943                            body,
1944                            shebang: None,
1945                        }),
1946                        source_map: CodeGenResultSourceMap::None,
1947                        comments: CodeGenResultComments::Empty,
1948                        is_esm: false,
1949                        strict: false,
1950                        original_source_map: CodeGenResultOriginalSourceMap::Single(None),
1951                        minify: MinifyType::NoMinify,
1952                        scope_hoisting_syntax_contexts: None,
1953                    }
1954                }
1955                ParseResult::NotFound => {
1956                    let path = ident.path().to_string().await?;
1957                    let msg = format!("Could not parse module '{path}', file not found");
1958                    let body = vec![
1959                        quote!(
1960                            "const e = new Error($msg);" as Stmt,
1961                            msg: Expr = Expr::Lit(msg.into()),
1962                        ),
1963                        quote!("e.code = 'MODULE_UNPARSABLE';" as Stmt),
1964                        quote!("throw e;" as Stmt),
1965                    ];
1966                    CodeGenResult {
1967                        program: Program::Script(Script {
1968                            span: DUMMY_SP,
1969                            body,
1970                            shebang: None,
1971                        }),
1972                        source_map: CodeGenResultSourceMap::None,
1973                        comments: CodeGenResultComments::Empty,
1974                        is_esm: false,
1975                        strict: false,
1976                        original_source_map: CodeGenResultOriginalSourceMap::Single(None),
1977                        minify: MinifyType::NoMinify,
1978                        scope_hoisting_syntax_contexts: None,
1979                    }
1980                }
1981            })
1982        },
1983    )
1984    .instrument(tracing::trace_span!(
1985        "process parse result",
1986        ident = display(ident.to_string().await?),
1987    ))
1988    .await
1989}
1990
1991/// Try to avoid cloning the AST and Globals by unwrapping the ReadRef (and cloning otherwise).
1992async fn with_consumed_parse_result<T>(
1993    parsed: Option<ResolvedVc<ParseResult>>,
1994    success: impl AsyncFnOnce(
1995        Program,
1996        &Arc<SourceMap>,
1997        &Arc<Globals>,
1998        Either<EvalContext, &'_ EvalContext>,
1999        Either<ImmutableComments, Arc<ImmutableComments>>,
2000    ) -> Result<T>,
2001    error: impl AsyncFnOnce(&ParseResult) -> Result<T>,
2002) -> Result<T> {
2003    let Some(parsed) = parsed else {
2004        let globals = Globals::new();
2005        let eval_context = GLOBALS.set(&globals, || EvalContext {
2006            unresolved_mark: Mark::new(),
2007            top_level_mark: Mark::new(),
2008            imports: Default::default(),
2009            force_free_values: Default::default(),
2010        });
2011        return success(
2012            Program::Module(swc_core::ecma::ast::Module::dummy()),
2013            &Default::default(),
2014            &Default::default(),
2015            Either::Left(eval_context),
2016            Either::Left(Default::default()),
2017        )
2018        .await;
2019    };
2020
2021    let parsed = parsed.final_read_hint().await?;
2022    match &*parsed {
2023        ParseResult::Ok { .. } => {
2024            let mut parsed = ReadRef::try_unwrap(parsed);
2025            let (program, source_map, globals, eval_context, comments) = match &mut parsed {
2026                Ok(ParseResult::Ok {
2027                    program,
2028                    source_map,
2029                    globals,
2030                    eval_context,
2031                    comments,
2032                }) => (
2033                    program.take(),
2034                    &*source_map,
2035                    &*globals,
2036                    Either::Left(std::mem::replace(
2037                        eval_context,
2038                        EvalContext {
2039                            unresolved_mark: eval_context.unresolved_mark,
2040                            top_level_mark: eval_context.top_level_mark,
2041                            imports: Default::default(),
2042                            force_free_values: Default::default(),
2043                        },
2044                    )),
2045                    match Arc::try_unwrap(take(comments)) {
2046                        Ok(comments) => Either::Left(comments),
2047                        Err(comments) => Either::Right(comments),
2048                    },
2049                ),
2050                Err(parsed) => {
2051                    let ParseResult::Ok {
2052                        program,
2053                        source_map,
2054                        globals,
2055                        eval_context,
2056                        comments,
2057                    } = &**parsed
2058                    else {
2059                        unreachable!();
2060                    };
2061                    (
2062                        program.clone(),
2063                        source_map,
2064                        globals,
2065                        Either::Right(eval_context),
2066                        Either::Right(comments.clone()),
2067                    )
2068                }
2069                _ => unreachable!(),
2070            };
2071
2072            success(program, source_map, globals, eval_context, comments).await
2073        }
2074        _ => error(&parsed).await,
2075    }
2076}
2077
2078async fn emit_content(
2079    content: CodeGenResult,
2080    additional_ids: SmallVec<[ResolvedVc<ModuleId>; 1]>,
2081) -> Result<Vc<EcmascriptModuleContent>> {
2082    let CodeGenResult {
2083        program,
2084        source_map,
2085        comments,
2086        is_esm,
2087        strict,
2088        original_source_map,
2089        minify,
2090        scope_hoisting_syntax_contexts: _,
2091    } = content;
2092
2093    let generate_source_map = source_map.is_some();
2094
2095    let mut bytes: Vec<u8> = vec![];
2096    // TODO: Insert this as a sourceless segment so that sourcemaps aren't affected.
2097    // = format!("/* {} */\n", self.module.path().to_string().await?).into_bytes();
2098
2099    let mut mappings = vec![];
2100
2101    let source_map = Arc::new(source_map);
2102
2103    {
2104        let mut wr = JsWriter::new(
2105            // unused anyway?
2106            Default::default(),
2107            "\n",
2108            &mut bytes,
2109            generate_source_map.then_some(&mut mappings),
2110        );
2111        if matches!(minify, MinifyType::Minify { .. }) {
2112            wr.set_indent_str("");
2113        }
2114
2115        let comments = comments.consumable();
2116
2117        let mut emitter = Emitter {
2118            cfg: swc_core::ecma::codegen::Config::default(),
2119            cm: source_map.clone(),
2120            comments: Some(&comments as &dyn Comments),
2121            wr,
2122        };
2123
2124        emitter.emit_program(&program)?;
2125        // Drop the AST eagerly so we don't keep it in memory while generating source maps
2126        drop(program);
2127    }
2128
2129    let source_map = if generate_source_map {
2130        Some(generate_js_source_map(
2131            &*source_map,
2132            mappings,
2133            original_source_map
2134                .iter()
2135                .map(|map| map.generate_source_map())
2136                .try_flat_join()
2137                .await?,
2138            matches!(
2139                original_source_map,
2140                CodeGenResultOriginalSourceMap::Single(_)
2141            ),
2142            true,
2143        )?)
2144    } else {
2145        None
2146    };
2147
2148    Ok(EcmascriptModuleContent {
2149        inner_code: bytes.into(),
2150        source_map,
2151        is_esm,
2152        strict,
2153        additional_ids,
2154    }
2155    .cell())
2156}
2157
2158#[instrument(level = Level::TRACE, skip_all, name = "apply code generation")]
2159fn process_content_with_code_gens(
2160    program: &mut Program,
2161    globals: &Globals,
2162    code_gens: &mut Vec<CodeGeneration>,
2163) {
2164    let mut visitors = Vec::new();
2165    let mut root_visitors = Vec::new();
2166    let mut early_hoisted_stmts = FxIndexMap::default();
2167    let mut hoisted_stmts = FxIndexMap::default();
2168    let mut early_late_stmts = FxIndexMap::default();
2169    let mut late_stmts = FxIndexMap::default();
2170    for code_gen in code_gens {
2171        for CodeGenerationHoistedStmt { key, stmt } in code_gen.hoisted_stmts.drain(..) {
2172            hoisted_stmts.entry(key).or_insert(stmt);
2173        }
2174        for CodeGenerationHoistedStmt { key, stmt } in code_gen.early_hoisted_stmts.drain(..) {
2175            early_hoisted_stmts.insert(key.clone(), stmt);
2176        }
2177        for CodeGenerationHoistedStmt { key, stmt } in code_gen.late_stmts.drain(..) {
2178            late_stmts.insert(key.clone(), stmt);
2179        }
2180        for CodeGenerationHoistedStmt { key, stmt } in code_gen.early_late_stmts.drain(..) {
2181            early_late_stmts.insert(key.clone(), stmt);
2182        }
2183        for (path, visitor) in &code_gen.visitors {
2184            if path.is_empty() {
2185                root_visitors.push(&**visitor);
2186            } else {
2187                visitors.push((path, &**visitor));
2188            }
2189        }
2190    }
2191
2192    GLOBALS.set(globals, || {
2193        if !visitors.is_empty() {
2194            program.visit_mut_with_ast_path(
2195                &mut ApplyVisitors::new(visitors),
2196                &mut Default::default(),
2197            );
2198        }
2199        for pass in root_visitors {
2200            program.modify(pass);
2201        }
2202    });
2203
2204    match program {
2205        Program::Module(ast::Module { body, .. }) => {
2206            body.splice(
2207                0..0,
2208                early_hoisted_stmts
2209                    .into_values()
2210                    .chain(hoisted_stmts.into_values())
2211                    .map(ModuleItem::Stmt),
2212            );
2213            body.extend(
2214                early_late_stmts
2215                    .into_values()
2216                    .chain(late_stmts.into_values())
2217                    .map(ModuleItem::Stmt),
2218            );
2219        }
2220        Program::Script(Script { body, .. }) => {
2221            body.splice(
2222                0..0,
2223                early_hoisted_stmts
2224                    .into_values()
2225                    .chain(hoisted_stmts.into_values()),
2226            );
2227            body.extend(
2228                early_late_stmts
2229                    .into_values()
2230                    .chain(late_stmts.into_values()),
2231            );
2232        }
2233    };
2234}
2235
2236/// Like `hygiene`, but only renames the Atoms without clearing all SyntaxContexts
2237///
2238/// Don't rename idents marked with `is_import_mark` (i.e. a reference to a value which is imported
2239/// from another merged module) or listed in `preserve_exports` (i.e. an exported local binding):
2240/// even if they are causing collisions, they will be handled by the next hygiene pass over the
2241/// whole module.
2242fn hygiene_rename_only(
2243    top_level_mark: Option<Mark>,
2244    is_import_mark: Mark,
2245    preserved_exports: &FxHashSet<Id>,
2246) -> impl VisitMut {
2247    struct HygieneRenamer<'a> {
2248        preserved_exports: &'a FxHashSet<Id>,
2249        is_import_mark: Mark,
2250    }
2251    // Copied from `hygiene_with_config`'s HygieneRenamer, but added an `preserved_exports`
2252    impl swc_core::ecma::transforms::base::rename::Renamer for HygieneRenamer<'_> {
2253        type Target = Id;
2254
2255        const MANGLE: bool = false;
2256        const RESET_N: bool = true;
2257
2258        fn new_name_for(&self, orig: &Id, n: &mut usize) -> Atom {
2259            let res = if *n == 0 {
2260                orig.0.clone()
2261            } else {
2262                format!("{}{}", orig.0, n).into()
2263            };
2264            *n += 1;
2265            res
2266        }
2267
2268        fn preserve_name(&self, orig: &Id) -> bool {
2269            self.preserved_exports.contains(orig) || orig.1.has_mark(self.is_import_mark)
2270        }
2271    }
2272    swc_core::ecma::transforms::base::rename::renamer_keep_contexts(
2273        swc_core::ecma::transforms::base::hygiene::Config {
2274            top_level_mark: top_level_mark.unwrap_or_default(),
2275            ..Default::default()
2276        },
2277        HygieneRenamer {
2278            preserved_exports,
2279            is_import_mark,
2280        },
2281    )
2282}
2283
2284#[derive(Default)]
2285enum CodeGenResultSourceMap {
2286    #[default]
2287    /// No source map should be generated for this module
2288    None,
2289    Single {
2290        source_map: Arc<SourceMap>,
2291    },
2292    ScopeHoisting {
2293        /// The bitwidth of the modules header in the spans, see
2294        /// [CodeGenResultComments::encode_bytepos]
2295        modules_header_width: u32,
2296        lookup_table: Arc<Mutex<Vec<ModulePosition>>>,
2297        source_maps: Vec<CodeGenResultSourceMap>,
2298    },
2299}
2300
2301impl CodeGenResultSourceMap {
2302    fn is_some(&self) -> bool {
2303        match self {
2304            CodeGenResultSourceMap::None => false,
2305            CodeGenResultSourceMap::Single { .. }
2306            | CodeGenResultSourceMap::ScopeHoisting { .. } => true,
2307        }
2308    }
2309}
2310
2311impl Debug for CodeGenResultSourceMap {
2312    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2313        match self {
2314            CodeGenResultSourceMap::None => write!(f, "CodeGenResultSourceMap::None"),
2315            CodeGenResultSourceMap::Single { source_map } => {
2316                write!(
2317                    f,
2318                    "CodeGenResultSourceMap::Single {{ source_map: {:?} }}",
2319                    source_map.files().clone()
2320                )
2321            }
2322            CodeGenResultSourceMap::ScopeHoisting {
2323                modules_header_width,
2324                source_maps,
2325                ..
2326            } => write!(
2327                f,
2328                "CodeGenResultSourceMap::ScopeHoisting {{ modules_header_width: \
2329                 {modules_header_width}, source_maps: {source_maps:?} }}",
2330            ),
2331        }
2332    }
2333}
2334
2335impl Files for CodeGenResultSourceMap {
2336    fn try_lookup_source_file(
2337        &self,
2338        pos: BytePos,
2339    ) -> Result<Option<Arc<SourceFile>>, SourceMapLookupError> {
2340        match self {
2341            CodeGenResultSourceMap::None => Ok(None),
2342            CodeGenResultSourceMap::Single { source_map } => source_map.try_lookup_source_file(pos),
2343            CodeGenResultSourceMap::ScopeHoisting {
2344                modules_header_width,
2345                lookup_table,
2346                source_maps,
2347            } => {
2348                let (module, pos) =
2349                    CodeGenResultComments::decode_bytepos(*modules_header_width, pos, lookup_table);
2350                source_maps[module].try_lookup_source_file(pos)
2351            }
2352        }
2353    }
2354
2355    fn is_in_file(&self, f: &Arc<SourceFile>, raw_pos: BytePos) -> bool {
2356        match self {
2357            CodeGenResultSourceMap::None => false,
2358            CodeGenResultSourceMap::Single { .. } => f.start_pos <= raw_pos && raw_pos < f.end_pos,
2359            CodeGenResultSourceMap::ScopeHoisting { .. } => {
2360                // let (module, pos) = CodeGenResultComments::decode_bytepos(*modules_header_width,
2361                // pos);
2362
2363                // TODO optimize this, unfortunately, `SourceFile` doesn't know which `module` it
2364                // belongs from.
2365                false
2366            }
2367        }
2368    }
2369
2370    fn map_raw_pos(&self, pos: BytePos) -> BytePos {
2371        match self {
2372            CodeGenResultSourceMap::None => BytePos::DUMMY,
2373            CodeGenResultSourceMap::Single { .. } => pos,
2374            CodeGenResultSourceMap::ScopeHoisting {
2375                modules_header_width,
2376                lookup_table,
2377                ..
2378            } => CodeGenResultComments::decode_bytepos(*modules_header_width, pos, lookup_table).1,
2379        }
2380    }
2381}
2382
2383impl SourceMapper for CodeGenResultSourceMap {
2384    fn lookup_char_pos(&self, pos: BytePos) -> Loc {
2385        match self {
2386            CodeGenResultSourceMap::None => {
2387                panic!("CodeGenResultSourceMap::None cannot lookup_char_pos")
2388            }
2389            CodeGenResultSourceMap::Single { source_map } => source_map.lookup_char_pos(pos),
2390            CodeGenResultSourceMap::ScopeHoisting {
2391                modules_header_width,
2392                lookup_table,
2393                source_maps,
2394            } => {
2395                let (module, pos) =
2396                    CodeGenResultComments::decode_bytepos(*modules_header_width, pos, lookup_table);
2397                source_maps[module].lookup_char_pos(pos)
2398            }
2399        }
2400    }
2401    fn span_to_lines(&self, sp: Span) -> FileLinesResult {
2402        match self {
2403            CodeGenResultSourceMap::None => {
2404                panic!("CodeGenResultSourceMap::None cannot span_to_lines")
2405            }
2406            CodeGenResultSourceMap::Single { source_map } => source_map.span_to_lines(sp),
2407            CodeGenResultSourceMap::ScopeHoisting {
2408                modules_header_width,
2409                lookup_table,
2410                source_maps,
2411            } => {
2412                let (module, lo) = CodeGenResultComments::decode_bytepos(
2413                    *modules_header_width,
2414                    sp.lo,
2415                    lookup_table,
2416                );
2417                source_maps[module].span_to_lines(Span {
2418                    lo,
2419                    hi: CodeGenResultComments::decode_bytepos(
2420                        *modules_header_width,
2421                        sp.hi,
2422                        lookup_table,
2423                    )
2424                    .1,
2425                })
2426            }
2427        }
2428    }
2429    fn span_to_string(&self, sp: Span) -> String {
2430        match self {
2431            CodeGenResultSourceMap::None => {
2432                panic!("CodeGenResultSourceMap::None cannot span_to_string")
2433            }
2434            CodeGenResultSourceMap::Single { source_map } => source_map.span_to_string(sp),
2435            CodeGenResultSourceMap::ScopeHoisting {
2436                modules_header_width,
2437                lookup_table,
2438                source_maps,
2439            } => {
2440                let (module, lo) = CodeGenResultComments::decode_bytepos(
2441                    *modules_header_width,
2442                    sp.lo,
2443                    lookup_table,
2444                );
2445                source_maps[module].span_to_string(Span {
2446                    lo,
2447                    hi: CodeGenResultComments::decode_bytepos(
2448                        *modules_header_width,
2449                        sp.hi,
2450                        lookup_table,
2451                    )
2452                    .1,
2453                })
2454            }
2455        }
2456    }
2457    fn span_to_filename(&self, sp: Span) -> Arc<FileName> {
2458        match self {
2459            CodeGenResultSourceMap::None => {
2460                panic!("CodeGenResultSourceMap::None cannot span_to_filename")
2461            }
2462            CodeGenResultSourceMap::Single { source_map } => source_map.span_to_filename(sp),
2463            CodeGenResultSourceMap::ScopeHoisting {
2464                modules_header_width,
2465                lookup_table,
2466                source_maps,
2467            } => {
2468                let (module, lo) = CodeGenResultComments::decode_bytepos(
2469                    *modules_header_width,
2470                    sp.lo,
2471                    lookup_table,
2472                );
2473                source_maps[module].span_to_filename(Span {
2474                    lo,
2475                    hi: CodeGenResultComments::decode_bytepos(
2476                        *modules_header_width,
2477                        sp.hi,
2478                        lookup_table,
2479                    )
2480                    .1,
2481                })
2482            }
2483        }
2484    }
2485    fn merge_spans(&self, sp_lhs: Span, sp_rhs: Span) -> Option<Span> {
2486        match self {
2487            CodeGenResultSourceMap::None => {
2488                panic!("CodeGenResultSourceMap::None cannot merge_spans")
2489            }
2490            CodeGenResultSourceMap::Single { source_map } => source_map.merge_spans(sp_lhs, sp_rhs),
2491            CodeGenResultSourceMap::ScopeHoisting {
2492                modules_header_width,
2493                lookup_table,
2494                source_maps,
2495            } => {
2496                let (module_lhs, lo_lhs) = CodeGenResultComments::decode_bytepos(
2497                    *modules_header_width,
2498                    sp_lhs.lo,
2499                    lookup_table,
2500                );
2501                let (module_rhs, lo_rhs) = CodeGenResultComments::decode_bytepos(
2502                    *modules_header_width,
2503                    sp_rhs.lo,
2504                    lookup_table,
2505                );
2506                if module_lhs != module_rhs {
2507                    return None;
2508                }
2509                source_maps[module_lhs].merge_spans(
2510                    Span {
2511                        lo: lo_lhs,
2512                        hi: CodeGenResultComments::decode_bytepos(
2513                            *modules_header_width,
2514                            sp_lhs.hi,
2515                            lookup_table,
2516                        )
2517                        .1,
2518                    },
2519                    Span {
2520                        lo: lo_rhs,
2521                        hi: CodeGenResultComments::decode_bytepos(
2522                            *modules_header_width,
2523                            sp_rhs.hi,
2524                            lookup_table,
2525                        )
2526                        .1,
2527                    },
2528                )
2529            }
2530        }
2531    }
2532    fn call_span_if_macro(&self, sp: Span) -> Span {
2533        match self {
2534            CodeGenResultSourceMap::None => {
2535                panic!("CodeGenResultSourceMap::None cannot call_span_if_macro")
2536            }
2537            CodeGenResultSourceMap::Single { source_map } => source_map.call_span_if_macro(sp),
2538            CodeGenResultSourceMap::ScopeHoisting {
2539                modules_header_width,
2540                lookup_table,
2541                source_maps,
2542            } => {
2543                let (module, lo) = CodeGenResultComments::decode_bytepos(
2544                    *modules_header_width,
2545                    sp.lo,
2546                    lookup_table,
2547                );
2548                source_maps[module].call_span_if_macro(Span {
2549                    lo,
2550                    hi: CodeGenResultComments::decode_bytepos(
2551                        *modules_header_width,
2552                        sp.hi,
2553                        lookup_table,
2554                    )
2555                    .1,
2556                })
2557            }
2558        }
2559    }
2560    fn doctest_offset_line(&self, _line: usize) -> usize {
2561        panic!("doctest_offset_line is not implemented for CodeGenResultSourceMap");
2562    }
2563    fn span_to_snippet(&self, sp: Span) -> Result<String, Box<SpanSnippetError>> {
2564        match self {
2565            CodeGenResultSourceMap::None => Err(Box::new(SpanSnippetError::SourceNotAvailable {
2566                filename: FileName::Anon,
2567            })),
2568            CodeGenResultSourceMap::Single { source_map } => source_map.span_to_snippet(sp),
2569            CodeGenResultSourceMap::ScopeHoisting {
2570                modules_header_width,
2571                lookup_table,
2572                source_maps,
2573            } => {
2574                let (module, lo) = CodeGenResultComments::decode_bytepos(
2575                    *modules_header_width,
2576                    sp.lo,
2577                    lookup_table,
2578                );
2579                source_maps[module].span_to_snippet(Span {
2580                    lo,
2581                    hi: CodeGenResultComments::decode_bytepos(
2582                        *modules_header_width,
2583                        sp.hi,
2584                        lookup_table,
2585                    )
2586                    .1,
2587                })
2588            }
2589        }
2590    }
2591}
2592impl SourceMapperExt for CodeGenResultSourceMap {
2593    fn get_code_map(&self) -> &dyn SourceMapper {
2594        self
2595    }
2596}
2597
2598#[derive(Debug)]
2599enum CodeGenResultOriginalSourceMap {
2600    Single(Option<ResolvedVc<Box<dyn GenerateSourceMap>>>),
2601    ScopeHoisting(SmallVec<[ResolvedVc<Box<dyn GenerateSourceMap>>; 1]>),
2602}
2603
2604impl CodeGenResultOriginalSourceMap {
2605    fn iter(&self) -> impl Iterator<Item = ResolvedVc<Box<dyn GenerateSourceMap>>> {
2606        match self {
2607            CodeGenResultOriginalSourceMap::Single(map) => Either::Left(map.iter().copied()),
2608            CodeGenResultOriginalSourceMap::ScopeHoisting(maps) => {
2609                Either::Right(maps.iter().copied())
2610            }
2611        }
2612    }
2613}
2614
2615/// Stores a module index in position 0 and the full byte position of the source map in position 1
2616struct ModulePosition(u32, u32);
2617
2618enum CodeGenResultComments {
2619    Single {
2620        comments: Either<ImmutableComments, Arc<ImmutableComments>>,
2621        extra_comments: SwcComments,
2622    },
2623    ScopeHoisting {
2624        /// The bitwidth of the modules header in the spans, see
2625        /// [CodeGenResultComments::encode_bytepos]
2626        modules_header_width: u32,
2627        lookup_table: Arc<Mutex<Vec<ModulePosition>>>,
2628        comments: Vec<CodeGenResultComments>,
2629    },
2630    Empty,
2631}
2632
2633unsafe impl Send for CodeGenResultComments {}
2634unsafe impl Sync for CodeGenResultComments {}
2635
2636impl CodeGenResultComments {
2637    const CONTINUATION_BIT: u32 = 1 << 31;
2638    const SIGN_EXTENSION_BIT: u32 = 1 << 30;
2639
2640    #[inline]
2641    fn encode_bytepos_impl(
2642        modules_header_width: u32,
2643        module: u32,
2644        pos: BytePos,
2645        push_into_lookup: &mut impl FnMut(u32, u32) -> Result<u32>,
2646    ) -> Result<BytePos> {
2647        if pos.is_dummy() {
2648            // nothing to encode
2649            return Ok(pos);
2650        }
2651
2652        // Bit layout for encoded BytePos (32 bits):
2653        // [31] Continuation bit. If set (1), the remaining 31 bits [0..30] encode an index into
2654        //      the lookup vector where (module, original_bytepos) is stored.
2655        //      In this case, decoding ignores other fields and fetches from the table.
2656        // If not set (0):
2657        // [30] Sign-extend bit. Indicates whether the stolen high bits of the original bytepos
2658        //      were all 1s (1) or all 0s (0), so that decoding can restore the original high bits.
2659        // [30 - modules_header_width + 1 .. 30) Module id: modules_header_width bits immediately
2660        //      below the sign-extend bit.
2661        // [0 .. (32 - (2 + modules_header_width)) ) Remaining low bits store the truncated bytepos.
2662        //
2663        // Notes:
2664        // - We reserve 2 header bits always (continuation + sign-extend), so header_width =
2665        //   modules_header_width + 2, and pos_width = 32 - header_width.
2666        // - When the original value does not fit in the available pos_width with a uniform high bit
2667        //   pattern, we spill (set continuation) and store (module, pos) in the lookup table and
2668        //   encode the index with the continuation bit set.
2669        //
2670        // Example (diagrammatic only):
2671        // modules_header_width = 4
2672        // Key:
2673        // (c = continuation, s = sign-extend, m = module, p = pos bits, i = lookup table index)
2674        //
2675        // The continuation bit is set, and the remaining 31 bits are reinterpreted as the index
2676        // into the lookup table.
2677        // Bytes: 1iii iiii iiii iiii iiii iiii iiii iiii
2678        //
2679        // The continuation bit is not set,
2680        // Bytes: 0smm mmpp pppp pppp pppp pppp pppp pppp
2681
2682        let header_width = modules_header_width + 2;
2683        let pos_width = 32 - header_width;
2684
2685        let pos = pos.0;
2686
2687        let old_high_bits = pos >> pos_width;
2688        let high_bits_set = if (2u32.pow(header_width) - 1) == old_high_bits {
2689            true
2690        } else if old_high_bits == 0 {
2691            false
2692        } else {
2693            // The integer is too large for our desired header width and we need to store the result
2694            // in our vector and set the flag to reinterpret this data as the index of
2695            // the vector where the element is being stored.
2696            let ix = push_into_lookup(module, pos)?;
2697            // Make sure that the index fits within the allotted bits
2698            assert_eq!(ix & CodeGenResultComments::CONTINUATION_BIT, 0);
2699
2700            return Ok(BytePos(ix | CodeGenResultComments::CONTINUATION_BIT));
2701        };
2702
2703        let pos = pos & !((2u32.pow(header_width) - 1) << pos_width);
2704        let encoded_high_bits = if high_bits_set {
2705            CodeGenResultComments::SIGN_EXTENSION_BIT
2706        } else {
2707            0
2708        };
2709        let encoded_module = module << pos_width;
2710
2711        Ok(BytePos(encoded_module | encoded_high_bits | pos))
2712    }
2713
2714    fn take(&mut self) -> Self {
2715        std::mem::replace(self, CodeGenResultComments::Empty)
2716    }
2717
2718    fn consumable(&self) -> CodeGenResultCommentsConsumable<'_> {
2719        match self {
2720            CodeGenResultComments::Single {
2721                comments,
2722                extra_comments,
2723            } => CodeGenResultCommentsConsumable::Single {
2724                comments: match comments {
2725                    Either::Left(comments) => comments.consumable(),
2726                    Either::Right(comments) => comments.consumable(),
2727                },
2728                extra_comments,
2729            },
2730            CodeGenResultComments::ScopeHoisting {
2731                modules_header_width,
2732                lookup_table,
2733                comments,
2734            } => CodeGenResultCommentsConsumable::ScopeHoisting {
2735                modules_header_width: *modules_header_width,
2736                lookup_table: lookup_table.clone(),
2737                comments: comments.iter().map(|c| c.consumable()).collect(),
2738            },
2739            CodeGenResultComments::Empty => CodeGenResultCommentsConsumable::Empty,
2740        }
2741    }
2742
2743    fn encode_bytepos(
2744        modules_header_width: u32,
2745        module: u32,
2746        pos: BytePos,
2747        lookup_table: Arc<Mutex<Vec<ModulePosition>>>,
2748    ) -> Result<BytePos> {
2749        let mut push = |module: u32, pos_u32: u32| -> Result<u32> {
2750            let mut lookup_table = lookup_table
2751                .lock()
2752                .map_err(|_| anyhow!("Failed to grab lock on the index map for byte positions"))?;
2753            let ix = lookup_table.len() as u32;
2754            if ix >= 1 << 30 {
2755                return Err(anyhow!("Too many byte positions being stored"));
2756            }
2757            lookup_table.push(ModulePosition(module, pos_u32));
2758            Ok(ix)
2759        };
2760        Self::encode_bytepos_impl(modules_header_width, module, pos, &mut push)
2761    }
2762
2763    fn encode_bytepos_with_vec(
2764        modules_header_width: u32,
2765        module: u32,
2766        pos: BytePos,
2767        lookup_table: &mut Vec<ModulePosition>,
2768    ) -> Result<BytePos> {
2769        let mut push = |module: u32, pos_u32: u32| -> Result<u32> {
2770            let ix = lookup_table.len() as u32;
2771            if ix >= 1 << 30 {
2772                return Err(anyhow!("Too many byte positions being stored"));
2773            }
2774            lookup_table.push(ModulePosition(module, pos_u32));
2775            Ok(ix)
2776        };
2777        Self::encode_bytepos_impl(modules_header_width, module, pos, &mut push)
2778    }
2779
2780    fn decode_bytepos(
2781        modules_header_width: u32,
2782        pos: BytePos,
2783        lookup_table: &Mutex<Vec<ModulePosition>>,
2784    ) -> (usize, BytePos) {
2785        if pos.is_dummy() {
2786            // nothing to decode
2787            panic!("Cannot decode dummy BytePos");
2788        }
2789
2790        let header_width = modules_header_width + 2;
2791        let pos_width = 32 - header_width;
2792
2793        if (CodeGenResultComments::CONTINUATION_BIT & pos.0)
2794            == CodeGenResultComments::CONTINUATION_BIT
2795        {
2796            let lookup_table = lookup_table
2797                .lock()
2798                .expect("Failed to grab lock on the index map for byte position");
2799            let ix = pos.0 & !CodeGenResultComments::CONTINUATION_BIT;
2800            let ModulePosition(module, pos) = lookup_table[ix as usize];
2801
2802            return (module as usize, BytePos(pos));
2803        }
2804
2805        let high_bits_set = pos.0 >> 30 & 1 == 1;
2806        let module = (pos.0 << 2) >> (pos_width + 2);
2807        let pos = pos.0 & !((2u32.pow(header_width) - 1) << pos_width);
2808        let pos = if high_bits_set {
2809            pos | ((2u32.pow(header_width) - 1) << pos_width)
2810        } else {
2811            pos
2812        };
2813        (module as usize, BytePos(pos))
2814    }
2815}
2816
2817enum CodeGenResultCommentsConsumable<'a> {
2818    Single {
2819        comments: CowComments<'a>,
2820        extra_comments: &'a SwcComments,
2821    },
2822    ScopeHoisting {
2823        modules_header_width: u32,
2824        lookup_table: Arc<Mutex<Vec<ModulePosition>>>,
2825        comments: Vec<CodeGenResultCommentsConsumable<'a>>,
2826    },
2827    Empty,
2828}
2829/// All BytePos in Spans in the AST are encoded correctly in [`merge_modules`], but the Comments
2830/// also contain spans. These also need to be encoded so that all pos in `mappings` are consistently
2831/// encoded.
2832fn encode_module_into_comment_span(
2833    modules_header_width: u32,
2834    module: usize,
2835    mut comment: Comment,
2836    lookup_table: Arc<Mutex<Vec<ModulePosition>>>,
2837) -> Comment {
2838    comment.span.lo = CodeGenResultComments::encode_bytepos(
2839        modules_header_width,
2840        module as u32,
2841        comment.span.lo,
2842        lookup_table.clone(),
2843    )
2844    .unwrap();
2845    comment.span.hi = CodeGenResultComments::encode_bytepos(
2846        modules_header_width,
2847        module as u32,
2848        comment.span.hi,
2849        lookup_table,
2850    )
2851    .unwrap();
2852    comment
2853}
2854
2855impl Comments for CodeGenResultCommentsConsumable<'_> {
2856    fn add_leading(&self, _pos: BytePos, _cmt: Comment) {
2857        unimplemented!("add_leading")
2858    }
2859
2860    fn add_leading_comments(&self, _pos: BytePos, _comments: Vec<Comment>) {
2861        unimplemented!("add_leading_comments")
2862    }
2863
2864    fn has_leading(&self, pos: BytePos) -> bool {
2865        if pos.is_dummy() {
2866            return false;
2867        }
2868        match self {
2869            Self::Single {
2870                comments,
2871                extra_comments,
2872            } => comments.has_leading(pos) || extra_comments.has_leading(pos),
2873            Self::ScopeHoisting {
2874                modules_header_width,
2875                lookup_table,
2876                comments,
2877            } => {
2878                let (module, pos) =
2879                    CodeGenResultComments::decode_bytepos(*modules_header_width, pos, lookup_table);
2880                comments[module].has_leading(pos)
2881            }
2882            Self::Empty => false,
2883        }
2884    }
2885
2886    fn move_leading(&self, _from: BytePos, _to: BytePos) {
2887        unimplemented!("move_leading")
2888    }
2889
2890    fn take_leading(&self, pos: BytePos) -> Option<Vec<Comment>> {
2891        if pos.is_dummy() {
2892            return None;
2893        }
2894        match self {
2895            Self::Single {
2896                comments,
2897                extra_comments,
2898            } => merge_option_vec(comments.take_leading(pos), extra_comments.take_leading(pos)),
2899            Self::ScopeHoisting {
2900                modules_header_width,
2901                lookup_table,
2902                comments,
2903            } => {
2904                let (module, pos) =
2905                    CodeGenResultComments::decode_bytepos(*modules_header_width, pos, lookup_table);
2906                comments[module].take_leading(pos).map(|comments| {
2907                    comments
2908                        .into_iter()
2909                        .map(|c| {
2910                            encode_module_into_comment_span(
2911                                *modules_header_width,
2912                                module,
2913                                c,
2914                                lookup_table.clone(),
2915                            )
2916                        })
2917                        .collect()
2918                })
2919            }
2920            Self::Empty => None,
2921        }
2922    }
2923
2924    fn get_leading(&self, pos: BytePos) -> Option<Vec<Comment>> {
2925        if pos.is_dummy() {
2926            return None;
2927        }
2928        match self {
2929            Self::Single {
2930                comments,
2931                extra_comments,
2932            } => merge_option_vec(comments.get_leading(pos), extra_comments.get_leading(pos)),
2933            Self::ScopeHoisting {
2934                modules_header_width,
2935                lookup_table,
2936                comments,
2937            } => {
2938                let (module, pos) =
2939                    CodeGenResultComments::decode_bytepos(*modules_header_width, pos, lookup_table);
2940                comments[module].get_leading(pos).map(|comments| {
2941                    comments
2942                        .into_iter()
2943                        .map(|c| {
2944                            encode_module_into_comment_span(
2945                                *modules_header_width,
2946                                module,
2947                                c,
2948                                lookup_table.clone(),
2949                            )
2950                        })
2951                        .collect()
2952                })
2953            }
2954            Self::Empty => None,
2955        }
2956    }
2957
2958    fn add_trailing(&self, _pos: BytePos, _cmt: Comment) {
2959        unimplemented!("add_trailing")
2960    }
2961
2962    fn add_trailing_comments(&self, _pos: BytePos, _comments: Vec<Comment>) {
2963        unimplemented!("add_trailing_comments")
2964    }
2965
2966    fn has_trailing(&self, pos: BytePos) -> bool {
2967        if pos.is_dummy() {
2968            return false;
2969        }
2970        match self {
2971            Self::Single {
2972                comments,
2973                extra_comments,
2974            } => comments.has_trailing(pos) || extra_comments.has_trailing(pos),
2975            Self::ScopeHoisting {
2976                modules_header_width,
2977                lookup_table,
2978                comments,
2979            } => {
2980                let (module, pos) =
2981                    CodeGenResultComments::decode_bytepos(*modules_header_width, pos, lookup_table);
2982                comments[module].has_trailing(pos)
2983            }
2984            Self::Empty => false,
2985        }
2986    }
2987
2988    fn move_trailing(&self, _from: BytePos, _to: BytePos) {
2989        unimplemented!("move_trailing")
2990    }
2991
2992    fn take_trailing(&self, pos: BytePos) -> Option<Vec<Comment>> {
2993        if pos.is_dummy() {
2994            return None;
2995        }
2996        match self {
2997            Self::Single {
2998                comments,
2999                extra_comments,
3000            } => merge_option_vec(
3001                comments.take_trailing(pos),
3002                extra_comments.take_trailing(pos),
3003            ),
3004            Self::ScopeHoisting {
3005                modules_header_width,
3006                lookup_table,
3007                comments,
3008            } => {
3009                let (module, pos) =
3010                    CodeGenResultComments::decode_bytepos(*modules_header_width, pos, lookup_table);
3011                comments[module].take_trailing(pos).map(|comments| {
3012                    comments
3013                        .into_iter()
3014                        .map(|c| {
3015                            encode_module_into_comment_span(
3016                                *modules_header_width,
3017                                module,
3018                                c,
3019                                lookup_table.clone(),
3020                            )
3021                        })
3022                        .collect()
3023                })
3024            }
3025            Self::Empty => None,
3026        }
3027    }
3028
3029    fn get_trailing(&self, pos: BytePos) -> Option<Vec<Comment>> {
3030        if pos.is_dummy() {
3031            return None;
3032        }
3033        match self {
3034            Self::Single {
3035                comments,
3036                extra_comments,
3037            } => merge_option_vec(comments.get_leading(pos), extra_comments.get_leading(pos)),
3038            Self::ScopeHoisting {
3039                modules_header_width,
3040                lookup_table,
3041                comments,
3042            } => {
3043                let (module, pos) =
3044                    CodeGenResultComments::decode_bytepos(*modules_header_width, pos, lookup_table);
3045                comments[module].get_leading(pos).map(|comments| {
3046                    comments
3047                        .into_iter()
3048                        .map(|c| {
3049                            encode_module_into_comment_span(
3050                                *modules_header_width,
3051                                module,
3052                                c,
3053                                lookup_table.clone(),
3054                            )
3055                        })
3056                        .collect()
3057                })
3058            }
3059            Self::Empty => None,
3060        }
3061    }
3062
3063    fn add_pure_comment(&self, _pos: BytePos) {
3064        unimplemented!("add_pure_comment")
3065    }
3066}
3067
3068fn merge_option_vec<T>(a: Option<Vec<T>>, b: Option<Vec<T>>) -> Option<Vec<T>> {
3069    match (a, b) {
3070        (Some(a), Some(b)) => Some(a.into_iter().chain(b).collect()),
3071        (Some(a), None) => Some(a),
3072        (None, Some(b)) => Some(b),
3073        (None, None) => None,
3074    }
3075}
3076
3077#[cfg(test)]
3078mod tests {
3079    use super::*;
3080    fn bytepos_ensure_identical(modules_header_width: u32, pos: BytePos) {
3081        let module_count = 2u32.pow(modules_header_width);
3082        let lookup_table = Arc::new(Mutex::new(Vec::new()));
3083
3084        for module in [
3085            0,
3086            1,
3087            2,
3088            module_count / 2,
3089            module_count.wrapping_sub(5),
3090            module_count.wrapping_sub(1),
3091        ]
3092        .into_iter()
3093        .filter(|&m| m < module_count)
3094        {
3095            let encoded = CodeGenResultComments::encode_bytepos(
3096                modules_header_width,
3097                module,
3098                pos,
3099                lookup_table.clone(),
3100            )
3101            .unwrap();
3102            let (decoded_module, decoded_pos) =
3103                CodeGenResultComments::decode_bytepos(modules_header_width, encoded, &lookup_table);
3104            assert_eq!(
3105                decoded_module as u32, module,
3106                "Testing width {modules_header_width} and pos {pos:?}"
3107            );
3108            assert_eq!(
3109                decoded_pos, pos,
3110                "Testing width {modules_header_width} and pos {pos:?}"
3111            );
3112        }
3113    }
3114
3115    #[test]
3116    fn test_encode_decode_bytepos_format() {
3117        let table = Arc::new(Mutex::new(Vec::new()));
3118
3119        for (pos, module, modules_header_width, result) in [
3120            (
3121                0b00000000000000000000000000000101,
3122                0b1,
3123                1,
3124                0b00100000000000000000000000000101,
3125            ),
3126            (
3127                0b00000000000000000000000000000101,
3128                0b01,
3129                2,
3130                0b00010000000000000000000000000101,
3131            ),
3132            (
3133                0b11111111111111110000000000000101,
3134                0b0110,
3135                4,
3136                0b01011011111111110000000000000101,
3137            ),
3138            (
3139                BytePos::PLACEHOLDER.0,
3140                0b01111,
3141                5,
3142                0b01011111111111111111111111111101,
3143            ),
3144            (
3145                BytePos::PURE.0,
3146                0b01111,
3147                5,
3148                0b01011111111111111111111111111110,
3149            ),
3150            (
3151                BytePos::SYNTHESIZED.0,
3152                0b01111,
3153                5,
3154                0b01011111111111111111111111111111,
3155            ),
3156            // This is an index that should trigger the overflow to store the position into the
3157            // lookup table
3158            (
3159                0b00000111111111110000000000000101,
3160                0b0001,
3161                4,
3162                0b10000000000000000000000000000000,
3163            ),
3164            // Another one should increase the index by 1
3165            (
3166                0b00000111111111110000000000111110,
3167                0b0001,
3168                4,
3169                0b10000000000000000000000000000001,
3170            ),
3171            // Special case, DUMMY stays a DUMMY
3172            (BytePos::DUMMY.0, 0b0001, 4, BytePos::DUMMY.0),
3173        ] {
3174            let encoded = CodeGenResultComments::encode_bytepos(
3175                modules_header_width,
3176                module,
3177                BytePos(pos),
3178                table.clone(),
3179            )
3180            .unwrap();
3181            assert_eq!(encoded.0, result);
3182
3183            // Ensure that the correct original module and bytepos are stored when overflow occurs
3184            if encoded.0 & CodeGenResultComments::CONTINUATION_BIT
3185                == CodeGenResultComments::CONTINUATION_BIT
3186            {
3187                let index = encoded.0 & !CodeGenResultComments::CONTINUATION_BIT;
3188                let ModulePosition(encoded_module, encoded_pos) =
3189                    table.lock().unwrap()[index as usize];
3190                assert_eq!(encoded_module, module);
3191                assert_eq!(encoded_pos, pos);
3192            }
3193        }
3194    }
3195
3196    #[test]
3197    fn test_encode_decode_bytepos_lossless() {
3198        // This is copied from swc (it's not exported), comments the range above this value.
3199        const DUMMY_RESERVE: u32 = u32::MAX - 2_u32.pow(16);
3200
3201        for modules_header_width in 1..=10 {
3202            for pos in [
3203                // BytePos::DUMMY, // This must never get decoded in the first place
3204                BytePos(1),
3205                BytePos(2),
3206                BytePos(100),
3207                BytePos(4_000_000),
3208                BytePos(600_000_000),
3209                BytePos(u32::MAX - 3), // The maximum allowed value that isn't reserved by SWC
3210                BytePos::PLACEHOLDER,
3211                BytePos::SYNTHESIZED,
3212                BytePos::PURE,
3213                BytePos(DUMMY_RESERVE),
3214                BytePos(DUMMY_RESERVE + 10),
3215                BytePos(DUMMY_RESERVE + 10000),
3216            ] {
3217                bytepos_ensure_identical(modules_header_width, pos);
3218            }
3219        }
3220    }
3221}