Skip to main content

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