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, NonLocalValue, ReadRef, ResolvedVc, TaskInput, TryJoinIterExt, Upcast,
82    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().to_resolved().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                .to_resolved()
826                .await
827                .map(|r| *r)
828        }
829        .instrument(span)
830        .await
831    }
832}
833
834#[turbo_tasks::value_impl]
835impl MergeableModule for EcmascriptModuleAsset {
836    #[turbo_tasks::function]
837    async fn is_mergeable(self: ResolvedVc<Self>) -> Result<Vc<bool>> {
838        if matches!(
839            &*self.get_exports().await?,
840            EcmascriptExports::EsmExports(_)
841        ) {
842            return Ok(Vc::cell(true));
843        }
844
845        Ok(Vc::cell(false))
846    }
847
848    #[turbo_tasks::function]
849    async fn merge(
850        self: Vc<Self>,
851        modules: Vc<MergeableModulesExposed>,
852        entry_points: Vc<MergeableModules>,
853    ) -> Result<Vc<Box<dyn ChunkableModule>>> {
854        Ok(Vc::upcast(
855            *MergedEcmascriptModule::new(
856                modules,
857                entry_points,
858                self.options().to_resolved().await?,
859            )
860            .await?,
861        ))
862    }
863}
864
865#[turbo_tasks::value_impl]
866impl EvaluatableAsset for EcmascriptModuleAsset {}
867
868#[turbo_tasks::value_impl]
869impl ResolveOrigin for EcmascriptModuleAsset {
870    #[turbo_tasks::function]
871    fn origin_path(&self) -> Vc<FileSystemPath> {
872        self.source.ident().path()
873    }
874
875    #[turbo_tasks::function]
876    fn asset_context(&self) -> Vc<Box<dyn AssetContext>> {
877        *self.asset_context
878    }
879
880    #[turbo_tasks::function]
881    async fn get_inner_asset(&self, request: Vc<Request>) -> Result<Vc<OptionModule>> {
882        Ok(Vc::cell(if let Some(inner_assets) = &self.inner_assets {
883            if let Some(request) = request.await?.request() {
884                inner_assets.await?.get(&request).copied()
885            } else {
886                None
887            }
888        } else {
889            None
890        }))
891    }
892}
893
894/// The transformed contents of an Ecmascript module.
895#[turbo_tasks::value(shared)]
896pub struct EcmascriptModuleContent {
897    pub inner_code: Rope,
898    pub source_map: Option<Rope>,
899    pub is_esm: bool,
900    pub strict: bool,
901    pub additional_ids: SmallVec<[ModuleId; 1]>,
902}
903
904#[turbo_tasks::value(shared)]
905#[derive(Clone, Debug, Hash, TaskInput)]
906pub struct EcmascriptModuleContentOptions {
907    module: ResolvedVc<Box<dyn EcmascriptChunkPlaceable>>,
908    parsed: Option<ResolvedVc<ParseResult>>,
909    specified_module_type: SpecifiedModuleType,
910    chunking_context: ResolvedVc<Box<dyn ChunkingContext>>,
911    references: ResolvedVc<ModuleReferences>,
912    part_references: Vec<ResolvedVc<EcmascriptModulePartReference>>,
913    esm_references: ResolvedVc<EsmAssetReferences>,
914    code_generation: ResolvedVc<CodeGens>,
915    async_module: ResolvedVc<OptionAsyncModule>,
916    generate_source_map: bool,
917    original_source_map: Option<ResolvedVc<Box<dyn GenerateSourceMap>>>,
918    exports: ResolvedVc<EcmascriptExports>,
919    async_module_info: Option<ResolvedVc<AsyncModuleInfo>>,
920}
921
922impl EcmascriptModuleContentOptions {
923    async fn merged_code_gens(
924        &self,
925        scope_hoisting_context: ScopeHoistingContext<'_>,
926        eval_context: &EvalContext,
927    ) -> Result<Vec<CodeGeneration>> {
928        // Don't read `parsed` here again, it will cause a recomputation as `process_parse_result`
929        // has consumed the cell already.
930        let EcmascriptModuleContentOptions {
931            module,
932            chunking_context,
933            references,
934            part_references,
935            esm_references,
936            code_generation,
937            async_module,
938            exports,
939            async_module_info,
940            ..
941        } = self;
942
943        async {
944            let additional_code_gens = [
945                if let Some(async_module) = &*async_module.await? {
946                    Some(
947                        async_module
948                            .code_generation(
949                                async_module_info.map(|info| *info),
950                                **references,
951                                **chunking_context,
952                            )
953                            .await?,
954                    )
955                } else {
956                    None
957                },
958                if let EcmascriptExports::EsmExports(exports) = *exports.await? {
959                    Some(
960                        exports
961                            .code_generation(
962                                **chunking_context,
963                                scope_hoisting_context,
964                                eval_context,
965                                *module,
966                            )
967                            .await?,
968                    )
969                } else {
970                    None
971                },
972            ];
973
974            let part_code_gens = part_references
975                .iter()
976                .map(|r| r.code_generation(**chunking_context, scope_hoisting_context))
977                .try_join()
978                .await?;
979
980            let esm_code_gens = esm_references
981                .await?
982                .iter()
983                .map(|r| r.code_generation(**chunking_context, scope_hoisting_context))
984                .try_join()
985                .await?;
986
987            let code_gens = code_generation
988                .await?
989                .iter()
990                .map(|c| {
991                    c.code_generation(
992                        **chunking_context,
993                        scope_hoisting_context,
994                        *module,
995                        *exports,
996                    )
997                })
998                .try_join()
999                .await?;
1000
1001            anyhow::Ok(
1002                part_code_gens
1003                    .into_iter()
1004                    .chain(esm_code_gens.into_iter())
1005                    .chain(additional_code_gens.into_iter().flatten())
1006                    .chain(code_gens.into_iter())
1007                    .collect(),
1008            )
1009        }
1010        .instrument(tracing::info_span!("precompute code generation"))
1011        .await
1012    }
1013}
1014
1015#[turbo_tasks::value_impl]
1016impl EcmascriptModuleContent {
1017    /// Creates a new [`Vc<EcmascriptModuleContent>`].
1018    #[turbo_tasks::function]
1019    pub async fn new(input: Vc<EcmascriptModuleContentOptions>) -> Result<Vc<Self>> {
1020        let input = input.await?;
1021        let EcmascriptModuleContentOptions {
1022            parsed,
1023            module,
1024            specified_module_type,
1025            generate_source_map,
1026            original_source_map,
1027            chunking_context,
1028            ..
1029        } = &*input;
1030
1031        let minify = chunking_context.minify_type().await?;
1032
1033        let content = process_parse_result(
1034            *parsed,
1035            module.ident(),
1036            *specified_module_type,
1037            *generate_source_map,
1038            *original_source_map,
1039            *minify,
1040            Some(&*input),
1041            None,
1042        )
1043        .await?;
1044        emit_content(content, Default::default()).await
1045    }
1046
1047    /// Creates a new [`Vc<EcmascriptModuleContent>`] without an analysis pass.
1048    #[turbo_tasks::function]
1049    pub async fn new_without_analysis(
1050        parsed: Vc<ParseResult>,
1051        ident: Vc<AssetIdent>,
1052        specified_module_type: SpecifiedModuleType,
1053        generate_source_map: bool,
1054    ) -> Result<Vc<Self>> {
1055        let content = process_parse_result(
1056            Some(parsed.to_resolved().await?),
1057            ident,
1058            specified_module_type,
1059            generate_source_map,
1060            None,
1061            MinifyType::NoMinify,
1062            None,
1063            None,
1064        )
1065        .await?;
1066        emit_content(content, Default::default()).await
1067    }
1068
1069    /// Creates a new [`Vc<EcmascriptModuleContent>`] from multiple modules, performing scope
1070    /// hoisting.
1071    /// - The `modules` argument is a list of all modules to be merged (and whether their exports
1072    ///   should be exposed).
1073    /// - The `entries` argument is a list of modules that should be treated as entry points for the
1074    ///   merged module (used to determine execution order).
1075    #[turbo_tasks::function]
1076    pub async fn new_merged(
1077        modules: Vec<(
1078            ResolvedVc<Box<dyn EcmascriptAnalyzable>>,
1079            MergeableModuleExposure,
1080        )>,
1081        module_options: Vec<Vc<EcmascriptModuleContentOptions>>,
1082        entry_points: Vec<ResolvedVc<Box<dyn EcmascriptAnalyzable>>>,
1083    ) -> Result<Vc<Self>> {
1084        async {
1085            let modules = modules
1086                .into_iter()
1087                .map(|(m, exposed)| {
1088                    (
1089                        ResolvedVc::try_sidecast::<Box<dyn EcmascriptChunkPlaceable>>(m).unwrap(),
1090                        exposed,
1091                    )
1092                })
1093                .collect::<FxIndexMap<_, _>>();
1094            let entry_points = entry_points
1095                .into_iter()
1096                .map(|m| {
1097                    let m =
1098                        ResolvedVc::try_sidecast::<Box<dyn EcmascriptChunkPlaceable>>(m).unwrap();
1099                    (m, modules.get_index_of(&m).unwrap())
1100                })
1101                .collect::<Vec<_>>();
1102
1103            let globals_merged = Globals::default();
1104
1105            let contents = module_options
1106                .iter()
1107                .map(async |options| {
1108                    let options = options.await?;
1109                    let EcmascriptModuleContentOptions {
1110                        chunking_context,
1111                        parsed,
1112                        module,
1113                        specified_module_type,
1114                        generate_source_map,
1115                        original_source_map,
1116                        ..
1117                    } = &*options;
1118
1119                    let result = process_parse_result(
1120                        *parsed,
1121                        module.ident(),
1122                        *specified_module_type,
1123                        *generate_source_map,
1124                        *original_source_map,
1125                        *chunking_context.minify_type().await?,
1126                        Some(&*options),
1127                        Some(ScopeHoistingOptions {
1128                            module: *module,
1129                            modules: &modules,
1130                        }),
1131                    )
1132                    .await?;
1133
1134                    Ok((*module, result))
1135                })
1136                .try_join()
1137                .await?;
1138
1139            let (merged_ast, comments, source_maps, original_source_maps, lookup_table) =
1140                merge_modules(contents, &entry_points, &globals_merged).await?;
1141
1142            // Use the options from an arbitrary module, since they should all be the same with
1143            // regards to minify_type and chunking_context.
1144            let options = module_options.last().unwrap().await?;
1145
1146            let modules_header_width = modules.len().next_power_of_two().trailing_zeros();
1147            let content = CodeGenResult {
1148                program: merged_ast,
1149                source_map: CodeGenResultSourceMap::ScopeHoisting {
1150                    modules_header_width,
1151                    lookup_table: lookup_table.clone(),
1152                    source_maps,
1153                },
1154                comments: CodeGenResultComments::ScopeHoisting {
1155                    modules_header_width,
1156                    lookup_table,
1157                    comments,
1158                },
1159                is_esm: true,
1160                strict: true,
1161                original_source_map: CodeGenResultOriginalSourceMap::ScopeHoisting(
1162                    original_source_maps,
1163                ),
1164                minify: *options.chunking_context.minify_type().await?,
1165                scope_hoisting_syntax_contexts: None,
1166            };
1167
1168            let first_entry = entry_points.first().unwrap().0;
1169            let additional_ids = modules
1170                .keys()
1171                // Additionally set this module factory for all modules that are exposed. The whole
1172                // group might be imported via a different entry import in different chunks (we only
1173                // ensure that the modules are in the same order, not that they form a subgraph that
1174                // is always imported from the same root module).
1175                //
1176                // Also skip the first entry, which is the name of the chunk item.
1177                .filter(|m| {
1178                    **m != first_entry
1179                        && *modules.get(*m).unwrap() == MergeableModuleExposure::External
1180                })
1181                .map(|m| m.chunk_item_id(*options.chunking_context))
1182                .try_join()
1183                .await?
1184                .into();
1185
1186            emit_content(content, additional_ids)
1187                .instrument(tracing::info_span!("emit code"))
1188                .await
1189        }
1190        .instrument(tracing::info_span!(
1191            "generate merged code",
1192            modules = module_options.len()
1193        ))
1194        .await
1195    }
1196}
1197
1198/// Merges multiple Ecmascript modules into a single AST, setting the syntax contexts correctly so
1199/// that imports work.
1200///
1201/// In `contents`, each import from another module in the group must have an Ident with
1202/// - a `ctxt` listed in scope_hoisting_syntax_contexts.module_contexts, and
1203/// - `sym` being the name of the import.
1204///
1205/// This is then used to map back to the variable name and context of the exporting module.
1206#[instrument(level = Level::TRACE, skip_all, name = "merge")]
1207#[allow(clippy::type_complexity)]
1208async fn merge_modules(
1209    mut contents: Vec<(ResolvedVc<Box<dyn EcmascriptChunkPlaceable>>, CodeGenResult)>,
1210    entry_points: &Vec<(ResolvedVc<Box<dyn EcmascriptChunkPlaceable>>, usize)>,
1211    globals_merged: &'_ Globals,
1212) -> Result<(
1213    Program,
1214    Vec<CodeGenResultComments>,
1215    Vec<CodeGenResultSourceMap>,
1216    SmallVec<[ResolvedVc<Box<dyn GenerateSourceMap>>; 1]>,
1217    Arc<Mutex<Vec<ModulePosition>>>,
1218)> {
1219    struct SetSyntaxContextVisitor<'a> {
1220        modules_header_width: u32,
1221        current_module: ResolvedVc<Box<dyn EcmascriptChunkPlaceable>>,
1222        current_module_idx: u32,
1223        lookup_table: &'a mut Vec<ModulePosition>,
1224        /// The export syntax contexts in the current AST, which will be mapped to merged_ctxts
1225        reverse_module_contexts:
1226            FxHashMap<SyntaxContext, ResolvedVc<Box<dyn EcmascriptChunkPlaceable>>>,
1227        /// For a given module, the `eval_context.imports.exports`. So for a given export, this
1228        /// allows looking up the corresponding local binding's name and context.
1229        export_contexts:
1230            &'a FxHashMap<ResolvedVc<Box<dyn EcmascriptChunkPlaceable>>, &'a FxHashMap<RcStr, Id>>,
1231        /// A fresh global SyntaxContext for each module-local context, so that we can merge them
1232        /// into a single global AST.
1233        unique_contexts_cache: &'a mut FxHashMap<
1234            (ResolvedVc<Box<dyn EcmascriptChunkPlaceable>>, SyntaxContext),
1235            SyntaxContext,
1236        >,
1237
1238        error: anyhow::Result<()>,
1239    }
1240
1241    impl<'a> SetSyntaxContextVisitor<'a> {
1242        fn get_context_for(
1243            &mut self,
1244            module: ResolvedVc<Box<dyn EcmascriptChunkPlaceable>>,
1245            local_ctxt: SyntaxContext,
1246        ) -> SyntaxContext {
1247            if let Some(&global_ctxt) = self.unique_contexts_cache.get(&(module, local_ctxt)) {
1248                global_ctxt
1249            } else {
1250                let global_ctxt = SyntaxContext::empty().apply_mark(Mark::new());
1251                self.unique_contexts_cache
1252                    .insert((module, local_ctxt), global_ctxt);
1253                global_ctxt
1254            }
1255        }
1256    }
1257
1258    impl VisitMut for SetSyntaxContextVisitor<'_> {
1259        fn visit_mut_ident(&mut self, ident: &mut Ident) {
1260            let Ident {
1261                sym, ctxt, span, ..
1262            } = ident;
1263
1264            // If this ident is an imported binding, rewrite the name and context to the
1265            // corresponding export in the module that exports it.
1266            if let Some(&module) = self.reverse_module_contexts.get(ctxt) {
1267                let eval_context_exports = self.export_contexts.get(&module).unwrap();
1268                // TODO looking up an Atom in a Map<RcStr, _>, would ideally work without creating a
1269                // RcStr every time.
1270                let sym_rc_str: RcStr = sym.as_str().into();
1271                let (local, local_ctxt) = if let Some((local, local_ctxt)) =
1272                    eval_context_exports.get(&sym_rc_str)
1273                {
1274                    (Some(local), *local_ctxt)
1275                } else if sym.starts_with("__TURBOPACK__imported__module__") {
1276                    // The variable corresponding to the `export * as foo from "...";` is generated
1277                    // in the module generating the reexport (and it's not listed in the
1278                    // eval_context). `EsmAssetReference::code_gen` uses a dummy span when
1279                    // generating this variable.
1280                    (None, SyntaxContext::empty())
1281                } else {
1282                    self.error = Err(anyhow::anyhow!(
1283                        "Expected to find a local export for {sym} with ctxt {ctxt:#?} in \
1284                         {eval_context_exports:?}",
1285                    ));
1286                    return;
1287                };
1288
1289                let global_ctxt = self.get_context_for(module, local_ctxt);
1290
1291                if let Some(local) = local {
1292                    *sym = local.clone();
1293                }
1294                *ctxt = global_ctxt;
1295                span.visit_mut_with(self);
1296            } else {
1297                ident.visit_mut_children_with(self);
1298            }
1299        }
1300
1301        fn visit_mut_syntax_context(&mut self, local_ctxt: &mut SyntaxContext) {
1302            // The modules have their own local syntax contexts, which needs to be mapped to
1303            // contexts that were actually created in the merged Globals.
1304            let module = self
1305                .reverse_module_contexts
1306                .get(local_ctxt)
1307                .copied()
1308                .unwrap_or(self.current_module);
1309
1310            let global_ctxt = self.get_context_for(module, *local_ctxt);
1311            *local_ctxt = global_ctxt;
1312        }
1313        fn visit_mut_span(&mut self, span: &mut Span) {
1314            // Encode the module index into the span, to be able to retrieve the module later for
1315            // finding the correct Comments and SourceMap.
1316            span.lo = CodeGenResultComments::encode_bytepos_with_vec(
1317                self.modules_header_width,
1318                self.current_module_idx,
1319                span.lo,
1320                self.lookup_table,
1321            )
1322            .unwrap_or_else(|err| {
1323                self.error = Err(err);
1324                span.lo
1325            });
1326            span.hi = CodeGenResultComments::encode_bytepos_with_vec(
1327                self.modules_header_width,
1328                self.current_module_idx,
1329                span.hi,
1330                self.lookup_table,
1331            )
1332            .unwrap_or_else(|err| {
1333                self.error = Err(err);
1334                span.hi
1335            });
1336        }
1337    }
1338
1339    // Extract programs into a separate mutable list so that `content` doesn't have to be mutably
1340    // borrowed (and `export_contexts` doesn't have to clone).
1341    let mut programs = contents
1342        .iter_mut()
1343        .map(|(_, content)| content.program.take())
1344        .collect::<Vec<_>>();
1345
1346    let export_contexts = contents
1347        .iter()
1348        .map(|(module, content)| {
1349            Ok((
1350                *module,
1351                content
1352                    .scope_hoisting_syntax_contexts
1353                    .as_ref()
1354                    .map(|(_, export_contexts)| export_contexts)
1355                    .context("expected exports contexts")?,
1356            ))
1357        })
1358        .collect::<Result<FxHashMap<_, _>>>()?;
1359
1360    let mut lookup_table = Vec::new();
1361    let result = GLOBALS.set(globals_merged, || {
1362        let _ = tracing::trace_span!("merge inner").entered();
1363        // As an optimization, assume an average number of 5 contexts per module.
1364        let mut unique_contexts_cache =
1365            FxHashMap::with_capacity_and_hasher(contents.len() * 5, Default::default());
1366
1367        let mut prepare_module =
1368            |module_count: usize,
1369             current_module_idx: usize,
1370             (module, content): &(ResolvedVc<Box<dyn EcmascriptChunkPlaceable>>, CodeGenResult),
1371             program: &mut Program,
1372             lookup_table: &mut Vec<ModulePosition>| {
1373                let _ = tracing::trace_span!("prepare module").entered();
1374                if let CodeGenResult {
1375                    scope_hoisting_syntax_contexts: Some((module_contexts, _)),
1376                    ..
1377                } = content
1378                {
1379                    let modules_header_width = module_count.next_power_of_two().trailing_zeros();
1380                    GLOBALS.set(globals_merged, || {
1381                        let mut visitor = SetSyntaxContextVisitor {
1382                            modules_header_width,
1383                            current_module: *module,
1384                            current_module_idx: current_module_idx as u32,
1385                            lookup_table,
1386                            reverse_module_contexts: module_contexts
1387                                .iter()
1388                                .map(|e| (*e.value(), *e.key()))
1389                                .collect(),
1390                            export_contexts: &export_contexts,
1391                            unique_contexts_cache: &mut unique_contexts_cache,
1392                            error: Ok(()),
1393                        };
1394                        program.visit_mut_with(&mut visitor);
1395                        visitor.error
1396                    })?;
1397
1398                    Ok(match program.take() {
1399                        Program::Module(module) => Either::Left(module.body.into_iter()),
1400                        // A module without any ModuleItem::ModuleDecl but a
1401                        // SpecifiedModuleType::EcmaScript can still contain a Module::Script.
1402                        Program::Script(script) => {
1403                            Either::Right(script.body.into_iter().map(ModuleItem::Stmt))
1404                        }
1405                    })
1406                } else {
1407                    bail!("Expected scope_hosting_syntax_contexts");
1408                }
1409            };
1410
1411        let mut inserted = FxHashSet::with_capacity_and_hasher(contents.len(), Default::default());
1412        // Start with inserting the entry points, and recursively inline all their imports.
1413        inserted.extend(entry_points.iter().map(|(_, i)| *i));
1414
1415        let mut inserted_imports = FxHashMap::default();
1416
1417        let span = tracing::trace_span!("merge ASTs");
1418        // Replace inserted `__turbopack_merged_esm__(i);` statements with the corresponding
1419        // ith-module.
1420        let mut queue = entry_points
1421            .iter()
1422            .map(|&(_, i)| {
1423                prepare_module(
1424                    contents.len(),
1425                    i,
1426                    &contents[i],
1427                    &mut programs[i],
1428                    &mut lookup_table,
1429                )
1430                .map_err(|err| (i, err))
1431            })
1432            .flatten_ok()
1433            .rev()
1434            .collect::<Result<Vec<_>, _>>()?;
1435        let mut result = vec![];
1436        while let Some(item) = queue.pop() {
1437            if let ModuleItem::Stmt(stmt) = &item {
1438                match stmt {
1439                    Stmt::Expr(ExprStmt { expr, .. }) => {
1440                        if let Expr::Call(CallExpr {
1441                            callee: Callee::Expr(callee),
1442                            args,
1443                            ..
1444                        }) = &**expr
1445                            && callee.is_ident_ref_to("__turbopack_merged_esm__")
1446                        {
1447                            let index =
1448                                args[0].expr.as_lit().unwrap().as_num().unwrap().value as usize;
1449
1450                            // Only insert once, otherwise the module was already executed
1451                            if inserted.insert(index) {
1452                                queue.extend(
1453                                    prepare_module(
1454                                        contents.len(),
1455                                        index,
1456                                        &contents[index],
1457                                        &mut programs[index],
1458                                        &mut lookup_table,
1459                                    )
1460                                    .map_err(|err| (index, err))?
1461                                    .into_iter()
1462                                    .rev(),
1463                                );
1464                            }
1465                            continue;
1466                        }
1467                    }
1468                    Stmt::Decl(Decl::Var(var)) => {
1469                        if let [decl] = &*var.decls
1470                            && let Some(name) = decl.name.as_ident()
1471                            && name.sym.starts_with("__TURBOPACK__imported__module__")
1472                        {
1473                            // var __TURBOPACK__imported__module__.. = __turbopack_context__.i(..);
1474
1475                            // Even if these imports are not side-effect free, they only execute
1476                            // once, so no need to insert multiple times.
1477                            match inserted_imports.entry(name.sym.clone()) {
1478                                Entry::Occupied(entry) => {
1479                                    // If the import was already inserted, we can skip it. The
1480                                    // variable mapping minifies better but is unfortunately
1481                                    // necessary as the syntax contexts of the two imports are
1482                                    // different.
1483                                    let entry_ctxt = *entry.get();
1484                                    let new = Ident::new(name.sym.clone(), DUMMY_SP, name.ctxt);
1485                                    let old = Ident::new(name.sym.clone(), DUMMY_SP, entry_ctxt);
1486                                    result.push(ModuleItem::Stmt(
1487                                        quote!("var $new = $old;" as Stmt,
1488                                            new: Ident = new,
1489                                            old: Ident = old
1490                                        ),
1491                                    ));
1492                                    continue;
1493                                }
1494                                Entry::Vacant(entry) => {
1495                                    entry.insert(name.ctxt);
1496                                }
1497                            }
1498                        }
1499                    }
1500                    _ => (),
1501                }
1502            }
1503
1504            result.push(item);
1505        }
1506        drop(span);
1507
1508        let span = tracing::trace_span!("hygiene").entered();
1509        let mut merged_ast = Program::Module(swc_core::ecma::ast::Module {
1510            body: result,
1511            span: DUMMY_SP,
1512            shebang: None,
1513        });
1514        merged_ast.visit_mut_with(&mut swc_core::ecma::transforms::base::hygiene::hygiene());
1515        drop(span);
1516
1517        Ok((merged_ast, inserted))
1518    });
1519
1520    let (merged_ast, inserted) = match result {
1521        Ok(v) => v,
1522        Err((content_idx, err)) => {
1523            return Err(
1524                // ast-grep-ignore: no-context-turbofmt
1525                err.context(turbofmt!("Processing {}", contents[content_idx].0.ident()).await?),
1526            );
1527        }
1528    };
1529
1530    if cfg!(debug_assertions) && inserted.len() != contents.len() {
1531        bail!(
1532            "Not all merged modules were inserted: {:?}",
1533            contents
1534                .iter()
1535                .enumerate()
1536                .map(async |(i, m)| Ok((inserted.contains(&i), m.0.ident().to_string().await?)))
1537                .try_join()
1538                .await?,
1539        );
1540    }
1541
1542    let comments = contents
1543        .iter_mut()
1544        .map(|(_, content)| content.comments.take())
1545        .collect::<Vec<_>>();
1546
1547    let source_maps = contents
1548        .iter_mut()
1549        .map(|(_, content)| std::mem::take(&mut content.source_map))
1550        .collect::<Vec<_>>();
1551
1552    let original_source_maps = contents
1553        .iter_mut()
1554        .flat_map(|(_, content)| match content.original_source_map {
1555            CodeGenResultOriginalSourceMap::ScopeHoisting(_) => unreachable!(
1556                "Didn't expect nested CodeGenResultOriginalSourceMap::ScopeHoisting: {:?}",
1557                content.original_source_map
1558            ),
1559            CodeGenResultOriginalSourceMap::Single(map) => map,
1560        })
1561        .collect();
1562
1563    Ok((
1564        merged_ast,
1565        comments,
1566        source_maps,
1567        original_source_maps,
1568        Arc::new(Mutex::new(lookup_table)),
1569    ))
1570}
1571
1572/// Provides information about the other modules in the current scope hoisting group.
1573///
1574/// Note that this object contains interior mutability to lazily create syntax contexts in
1575/// `get_module_syntax_context`.
1576#[derive(Clone, Copy)]
1577pub enum ScopeHoistingContext<'a> {
1578    Some {
1579        /// The current module when scope hoisting
1580        module: ResolvedVc<Box<dyn EcmascriptChunkPlaceable>>,
1581        /// All modules in the current group, and whether they should expose their exports
1582        modules:
1583            &'a FxIndexMap<ResolvedVc<Box<dyn EcmascriptChunkPlaceable>>, MergeableModuleExposure>,
1584
1585        is_import_mark: Mark,
1586        globals: &'a Arc<Globals>,
1587        // Interior mutability!
1588        module_syntax_contexts_cache:
1589            &'a FxDashMap<ResolvedVc<Box<dyn EcmascriptChunkPlaceable>>, SyntaxContext>,
1590    },
1591    None,
1592}
1593
1594impl<'a> ScopeHoistingContext<'a> {
1595    /// The current module when scope hoisting
1596    pub fn module(&self) -> Option<ResolvedVc<Box<dyn EcmascriptChunkPlaceable>>> {
1597        match self {
1598            ScopeHoistingContext::Some { module, .. } => Some(*module),
1599            ScopeHoistingContext::None => None,
1600        }
1601    }
1602
1603    /// Whether the current module should not expose it's exports into the module cache.
1604    pub fn skip_module_exports(&self) -> bool {
1605        match self {
1606            ScopeHoistingContext::Some {
1607                module, modules, ..
1608            } => match modules.get(module).unwrap() {
1609                MergeableModuleExposure::None => true,
1610                MergeableModuleExposure::Internal | MergeableModuleExposure::External => false,
1611            },
1612            ScopeHoistingContext::None => false,
1613        }
1614    }
1615
1616    /// To import a specifier from another module, apply this context to the Ident
1617    pub fn get_module_syntax_context(
1618        &self,
1619        module: ResolvedVc<Box<dyn EcmascriptChunkPlaceable>>,
1620    ) -> Option<SyntaxContext> {
1621        match self {
1622            ScopeHoistingContext::Some {
1623                modules,
1624                module_syntax_contexts_cache,
1625                globals,
1626                is_import_mark,
1627                ..
1628            } => {
1629                if !modules.contains_key(&module) {
1630                    return None;
1631                }
1632
1633                Some(match module_syntax_contexts_cache.entry(module) {
1634                    dashmap::Entry::Occupied(e) => *e.get(),
1635                    dashmap::Entry::Vacant(e) => {
1636                        let ctxt = GLOBALS.set(globals, || {
1637                            let mark = Mark::fresh(*is_import_mark);
1638                            SyntaxContext::empty()
1639                                .apply_mark(*is_import_mark)
1640                                .apply_mark(mark)
1641                        });
1642
1643                        e.insert(ctxt);
1644                        ctxt
1645                    }
1646                })
1647            }
1648            ScopeHoistingContext::None => None,
1649        }
1650    }
1651
1652    pub fn get_module_index(
1653        &self,
1654        module: ResolvedVc<Box<dyn EcmascriptChunkPlaceable>>,
1655    ) -> Option<usize> {
1656        match self {
1657            ScopeHoistingContext::Some { modules, .. } => modules.get_index_of(&module),
1658            ScopeHoistingContext::None => None,
1659        }
1660    }
1661}
1662
1663struct CodeGenResult {
1664    program: Program,
1665    source_map: CodeGenResultSourceMap,
1666    comments: CodeGenResultComments,
1667    is_esm: bool,
1668    strict: bool,
1669    original_source_map: CodeGenResultOriginalSourceMap,
1670    minify: MinifyType,
1671    #[allow(clippy::type_complexity)]
1672    /// (Map<Module, corresponding context for imports>, `eval_context.imports.exports`)
1673    scope_hoisting_syntax_contexts: Option<(
1674        FxDashMap<ResolvedVc<Box<dyn EcmascriptChunkPlaceable + 'static>>, SyntaxContext>,
1675        FxHashMap<RcStr, Id>,
1676    )>,
1677}
1678
1679struct ScopeHoistingOptions<'a> {
1680    module: ResolvedVc<Box<dyn EcmascriptChunkPlaceable>>,
1681    modules: &'a FxIndexMap<ResolvedVc<Box<dyn EcmascriptChunkPlaceable>>, MergeableModuleExposure>,
1682}
1683
1684async fn process_parse_result(
1685    parsed: Option<ResolvedVc<ParseResult>>,
1686    ident: Vc<AssetIdent>,
1687    specified_module_type: SpecifiedModuleType,
1688    generate_source_map: bool,
1689    original_source_map: Option<ResolvedVc<Box<dyn GenerateSourceMap>>>,
1690    minify: MinifyType,
1691    options: Option<&EcmascriptModuleContentOptions>,
1692    scope_hoisting_options: Option<ScopeHoistingOptions<'_>>,
1693) -> Result<CodeGenResult> {
1694    with_consumed_parse_result(
1695        parsed,
1696        async |mut program, source_map, globals, eval_context, comments| -> Result<CodeGenResult> {
1697            let (top_level_mark, is_esm, strict) = eval_context
1698                .as_ref()
1699                .map_either(
1700                    |e| {
1701                        (
1702                            e.top_level_mark,
1703                            e.is_esm(specified_module_type),
1704                            e.imports.strict,
1705                        )
1706                    },
1707                    |e| {
1708                        (
1709                            e.top_level_mark,
1710                            e.is_esm(specified_module_type),
1711                            e.imports.strict,
1712                        )
1713                    },
1714                )
1715                .into_inner();
1716
1717            let (mut code_gens, retain_syntax_context, prepend_ident_comment) =
1718                if let Some(scope_hoisting_options) = scope_hoisting_options {
1719                    let is_import_mark = GLOBALS.set(globals, || Mark::new());
1720
1721                    let module_syntax_contexts_cache = FxDashMap::default();
1722                    let ctx = ScopeHoistingContext::Some {
1723                        module: scope_hoisting_options.module,
1724                        modules: scope_hoisting_options.modules,
1725                        module_syntax_contexts_cache: &module_syntax_contexts_cache,
1726                        is_import_mark,
1727                        globals,
1728                    };
1729                    let code_gens = options
1730                        .unwrap()
1731                        .merged_code_gens(
1732                            ctx,
1733                            match &eval_context {
1734                                Either::Left(e) => e,
1735                                Either::Right(e) => e,
1736                            },
1737                        )
1738                        .await?;
1739
1740                    let export_contexts = eval_context
1741                        .map_either(
1742                            |e| Cow::Owned(e.imports.exports),
1743                            |e| Cow::Borrowed(&e.imports.exports),
1744                        )
1745                        .into_inner();
1746                    let preserved_exports =
1747                        match &*scope_hoisting_options.module.get_exports().await? {
1748                            EcmascriptExports::EsmExports(exports) => exports
1749                                .await?
1750                                .exports
1751                                .iter()
1752                                .filter(|(_, e)| matches!(e, export::EsmExport::LocalBinding(_, _)))
1753                                .map(|(name, e)| {
1754                                    if let Some((sym, ctxt)) = export_contexts.get(name) {
1755                                        Ok((sym.clone(), *ctxt))
1756                                    } else {
1757                                        bail!("Couldn't find export {} for binding {:?}", name, e);
1758                                    }
1759                                })
1760                                .collect::<Result<FxHashSet<_>>>()?,
1761                            _ => Default::default(),
1762                        };
1763
1764                    let prepend_ident_comment = if matches!(minify, MinifyType::NoMinify) {
1765                        Some(Comment {
1766                            kind: CommentKind::Line,
1767                            span: DUMMY_SP,
1768                            text: (&*turbofmt!(" MERGED MODULE: {}", ident).await?).into(),
1769                        })
1770                    } else {
1771                        None
1772                    };
1773
1774                    (
1775                        code_gens,
1776                        Some((
1777                            is_import_mark,
1778                            module_syntax_contexts_cache,
1779                            preserved_exports,
1780                            export_contexts,
1781                        )),
1782                        prepend_ident_comment,
1783                    )
1784                } else if let Some(options) = options {
1785                    (
1786                        options
1787                            .merged_code_gens(
1788                                ScopeHoistingContext::None,
1789                                match &eval_context {
1790                                    Either::Left(e) => e,
1791                                    Either::Right(e) => e,
1792                                },
1793                            )
1794                            .await?,
1795                        None,
1796                        None,
1797                    )
1798                } else {
1799                    (vec![], None, None)
1800                };
1801
1802            let extra_comments = SwcComments {
1803                leading: Default::default(),
1804                trailing: Default::default(),
1805            };
1806
1807            process_content_with_code_gens(&mut program, globals, &mut code_gens);
1808
1809            for comments in code_gens.iter_mut().flat_map(|cg| cg.comments.as_mut()) {
1810                let leading = Arc::unwrap_or_clone(take(&mut comments.leading));
1811                let trailing = Arc::unwrap_or_clone(take(&mut comments.trailing));
1812
1813                for (pos, v) in leading {
1814                    extra_comments.leading.entry(pos).or_default().extend(v);
1815                }
1816
1817                for (pos, v) in trailing {
1818                    extra_comments.trailing.entry(pos).or_default().extend(v);
1819                }
1820            }
1821
1822            GLOBALS.set(globals, || {
1823                if let Some(prepend_ident_comment) = prepend_ident_comment {
1824                    let span = Span::dummy_with_cmt();
1825                    extra_comments.add_leading(span.lo, prepend_ident_comment);
1826                    let stmt = Stmt::Empty(EmptyStmt { span });
1827                    match &mut program {
1828                        Program::Module(module) => module.body.prepend_stmt(ModuleItem::Stmt(stmt)),
1829                        Program::Script(script) => script.body.prepend_stmt(stmt),
1830                    }
1831                }
1832
1833                if let Some((is_import_mark, _, preserved_exports, _)) = &retain_syntax_context {
1834                    program.visit_mut_with(&mut hygiene_rename_only(
1835                        Some(top_level_mark),
1836                        *is_import_mark,
1837                        preserved_exports,
1838                    ));
1839                } else {
1840                    program.visit_mut_with(
1841                        &mut swc_core::ecma::transforms::base::hygiene::hygiene_with_config(
1842                            swc_core::ecma::transforms::base::hygiene::Config {
1843                                top_level_mark,
1844                                ..Default::default()
1845                            },
1846                        ),
1847                    );
1848                }
1849                program.visit_mut_with(&mut swc_core::ecma::transforms::base::fixer::fixer(None));
1850
1851                // we need to remove any shebang before bundling as it's only valid as the first
1852                // line in a js file (not in a chunk item wrapped in the runtime)
1853                remove_shebang(&mut program);
1854                remove_directives(&mut program);
1855            });
1856
1857            Ok(CodeGenResult {
1858                program,
1859                source_map: if generate_source_map {
1860                    CodeGenResultSourceMap::Single {
1861                        source_map: source_map.clone(),
1862                    }
1863                } else {
1864                    CodeGenResultSourceMap::None
1865                },
1866                comments: CodeGenResultComments::Single {
1867                    comments,
1868                    extra_comments,
1869                },
1870                is_esm,
1871                strict,
1872                original_source_map: CodeGenResultOriginalSourceMap::Single(original_source_map),
1873                minify,
1874                scope_hoisting_syntax_contexts: retain_syntax_context
1875                    // TODO ideally don't clone here
1876                    .map(|(_, ctxts, _, export_contexts)| (ctxts, export_contexts.into_owned())),
1877            })
1878        },
1879        async |parse_result| -> Result<CodeGenResult> {
1880            Ok(match parse_result {
1881                ParseResult::Ok { .. } => unreachable!(),
1882                ParseResult::Unparsable { messages } => {
1883                    let error_messages = messages
1884                        .as_ref()
1885                        .and_then(|m| m.first().map(|f| format!("\n{f}")))
1886                        .unwrap_or("".into());
1887                    let msg = &*turbofmt!(
1888                        "Could not parse module '{}'\n{error_messages}",
1889                        ident.path()
1890                    )
1891                    .await?;
1892                    let body = vec![
1893                        quote!(
1894                            "const e = new Error($msg);" as Stmt,
1895                            msg: Expr = Expr::Lit(msg.into()),
1896                        ),
1897                        quote!("e.code = 'MODULE_UNPARSABLE';" as Stmt),
1898                        quote!("throw e;" as Stmt),
1899                    ];
1900
1901                    CodeGenResult {
1902                        program: Program::Script(Script {
1903                            span: DUMMY_SP,
1904                            body,
1905                            shebang: None,
1906                        }),
1907                        source_map: CodeGenResultSourceMap::None,
1908                        comments: CodeGenResultComments::Empty,
1909                        is_esm: false,
1910                        strict: false,
1911                        original_source_map: CodeGenResultOriginalSourceMap::Single(None),
1912                        minify: MinifyType::NoMinify,
1913                        scope_hoisting_syntax_contexts: None,
1914                    }
1915                }
1916                ParseResult::NotFound => {
1917                    let msg =
1918                        &*turbofmt!("Could not parse module '{}', file not found", ident.path())
1919                            .await?;
1920                    let body = vec![
1921                        quote!(
1922                            "const e = new Error($msg);" as Stmt,
1923                            msg: Expr = Expr::Lit(msg.into()),
1924                        ),
1925                        quote!("e.code = 'MODULE_UNPARSABLE';" as Stmt),
1926                        quote!("throw e;" as Stmt),
1927                    ];
1928                    CodeGenResult {
1929                        program: Program::Script(Script {
1930                            span: DUMMY_SP,
1931                            body,
1932                            shebang: None,
1933                        }),
1934                        source_map: CodeGenResultSourceMap::None,
1935                        comments: CodeGenResultComments::Empty,
1936                        is_esm: false,
1937                        strict: false,
1938                        original_source_map: CodeGenResultOriginalSourceMap::Single(None),
1939                        minify: MinifyType::NoMinify,
1940                        scope_hoisting_syntax_contexts: None,
1941                    }
1942                }
1943            })
1944        },
1945    )
1946    .instrument(tracing::trace_span!(
1947        "process parse result",
1948        ident = display(ident.to_string().await?),
1949    ))
1950    .await
1951}
1952
1953/// Try to avoid cloning the AST and Globals by unwrapping the ReadRef (and cloning otherwise).
1954async fn with_consumed_parse_result<T>(
1955    parsed: Option<ResolvedVc<ParseResult>>,
1956    success: impl AsyncFnOnce(
1957        Program,
1958        &Arc<SourceMap>,
1959        &Arc<Globals>,
1960        Either<EvalContext, &'_ EvalContext>,
1961        Either<ImmutableComments, Arc<ImmutableComments>>,
1962    ) -> Result<T>,
1963    error: impl AsyncFnOnce(&ParseResult) -> Result<T>,
1964) -> Result<T> {
1965    let Some(parsed) = parsed else {
1966        let globals = Globals::new();
1967        let eval_context = GLOBALS.set(&globals, || EvalContext {
1968            unresolved_mark: Mark::new(),
1969            top_level_mark: Mark::new(),
1970            imports: Default::default(),
1971            force_free_values: Default::default(),
1972        });
1973        return success(
1974            Program::Module(swc_core::ecma::ast::Module::dummy()),
1975            &Default::default(),
1976            &Default::default(),
1977            Either::Left(eval_context),
1978            Either::Left(Default::default()),
1979        )
1980        .await;
1981    };
1982
1983    let parsed = parsed.final_read_hint().await?;
1984    match &*parsed {
1985        ParseResult::Ok { .. } => {
1986            let mut parsed = ReadRef::try_unwrap(parsed);
1987            let (program, source_map, globals, eval_context, comments) = match &mut parsed {
1988                Ok(ParseResult::Ok {
1989                    program,
1990                    source_map,
1991                    globals,
1992                    eval_context,
1993                    comments,
1994                    ..
1995                }) => (
1996                    program.take(),
1997                    &*source_map,
1998                    &*globals,
1999                    Either::Left(std::mem::replace(
2000                        eval_context,
2001                        EvalContext {
2002                            unresolved_mark: eval_context.unresolved_mark,
2003                            top_level_mark: eval_context.top_level_mark,
2004                            imports: Default::default(),
2005                            force_free_values: Default::default(),
2006                        },
2007                    )),
2008                    match Arc::try_unwrap(take(comments)) {
2009                        Ok(comments) => Either::Left(comments),
2010                        Err(comments) => Either::Right(comments),
2011                    },
2012                ),
2013                Err(parsed) => {
2014                    let ParseResult::Ok {
2015                        program,
2016                        source_map,
2017                        globals,
2018                        eval_context,
2019                        comments,
2020                        ..
2021                    } = &**parsed
2022                    else {
2023                        unreachable!();
2024                    };
2025                    (
2026                        program.clone(),
2027                        source_map,
2028                        globals,
2029                        Either::Right(eval_context),
2030                        Either::Right(comments.clone()),
2031                    )
2032                }
2033                _ => unreachable!(),
2034            };
2035
2036            success(program, source_map, globals, eval_context, comments).await
2037        }
2038        _ => error(&parsed).await,
2039    }
2040}
2041
2042async fn emit_content(
2043    content: CodeGenResult,
2044    additional_ids: SmallVec<[ModuleId; 1]>,
2045) -> Result<Vc<EcmascriptModuleContent>> {
2046    let CodeGenResult {
2047        program,
2048        source_map,
2049        comments,
2050        is_esm,
2051        strict,
2052        original_source_map,
2053        minify,
2054        scope_hoisting_syntax_contexts: _,
2055    } = content;
2056
2057    let generate_source_map = source_map.is_some();
2058
2059    // Collect identifier names for source maps before emitting
2060    let source_map_names = if generate_source_map {
2061        let mut collector = IdentCollector::default();
2062        program.visit_with(&mut collector);
2063        collector.into_map()
2064    } else {
2065        Default::default()
2066    };
2067
2068    let mut bytes: Vec<u8> = vec![];
2069    // TODO: Insert this as a sourceless segment so that sourcemaps aren't affected.
2070    // = format!("/* {} */\n", self.module.path().to_string().await?).into_bytes();
2071
2072    let mut mappings = vec![];
2073
2074    let source_map = Arc::new(source_map);
2075
2076    {
2077        let mut wr = JsWriter::new(
2078            // unused anyway?
2079            Default::default(),
2080            "\n",
2081            &mut bytes,
2082            generate_source_map.then_some(&mut mappings),
2083        );
2084        if matches!(minify, MinifyType::Minify { .. }) {
2085            wr.set_indent_str("");
2086        }
2087
2088        let comments = comments.consumable();
2089
2090        let mut emitter = Emitter {
2091            cfg: swc_core::ecma::codegen::Config::default(),
2092            cm: source_map.clone(),
2093            comments: Some(&comments as &dyn Comments),
2094            wr,
2095        };
2096
2097        emitter.emit_program(&program)?;
2098        // Drop the AST eagerly so we don't keep it in memory while generating source maps
2099        drop(program);
2100    }
2101
2102    let source_map = if generate_source_map {
2103        let original_source_maps = original_source_map
2104            .iter()
2105            .map(|map| map.generate_source_map())
2106            .try_join()
2107            .await?;
2108        let original_source_maps = original_source_maps
2109            .iter()
2110            .filter_map(|map| map.as_content())
2111            .map(|map| map.content())
2112            .collect::<Vec<_>>();
2113
2114        Some(generate_js_source_map(
2115            &*source_map,
2116            mappings,
2117            original_source_maps,
2118            matches!(
2119                original_source_map,
2120                CodeGenResultOriginalSourceMap::Single(_)
2121            ),
2122            true,
2123            source_map_names,
2124        )?)
2125    } else {
2126        None
2127    };
2128
2129    Ok(EcmascriptModuleContent {
2130        inner_code: bytes.into(),
2131        source_map,
2132        is_esm,
2133        strict,
2134        additional_ids,
2135    }
2136    .cell())
2137}
2138
2139#[instrument(level = Level::TRACE, skip_all, name = "apply code generation")]
2140fn process_content_with_code_gens(
2141    program: &mut Program,
2142    globals: &Globals,
2143    code_gens: &mut Vec<CodeGeneration>,
2144) {
2145    let mut visitors = Vec::new();
2146    let mut root_visitors = Vec::new();
2147    let mut early_hoisted_stmts = FxIndexMap::default();
2148    let mut hoisted_stmts = FxIndexMap::default();
2149    let mut early_late_stmts = FxIndexMap::default();
2150    let mut late_stmts = FxIndexMap::default();
2151    for code_gen in code_gens {
2152        for CodeGenerationHoistedStmt { key, stmt } in code_gen.hoisted_stmts.drain(..) {
2153            hoisted_stmts.entry(key).or_insert(stmt);
2154        }
2155        for CodeGenerationHoistedStmt { key, stmt } in code_gen.early_hoisted_stmts.drain(..) {
2156            early_hoisted_stmts.insert(key.clone(), stmt);
2157        }
2158        for CodeGenerationHoistedStmt { key, stmt } in code_gen.late_stmts.drain(..) {
2159            late_stmts.insert(key.clone(), stmt);
2160        }
2161        for CodeGenerationHoistedStmt { key, stmt } in code_gen.early_late_stmts.drain(..) {
2162            early_late_stmts.insert(key.clone(), stmt);
2163        }
2164        for (path, visitor) in &code_gen.visitors {
2165            if path.is_empty() {
2166                root_visitors.push(&**visitor);
2167            } else {
2168                visitors.push((path, &**visitor));
2169            }
2170        }
2171    }
2172
2173    GLOBALS.set(globals, || {
2174        if !visitors.is_empty() {
2175            program.visit_mut_with_ast_path(
2176                &mut ApplyVisitors::new(visitors),
2177                &mut Default::default(),
2178            );
2179        }
2180        for pass in root_visitors {
2181            program.modify(pass);
2182        }
2183    });
2184
2185    match program {
2186        Program::Module(ast::Module { body, .. }) => {
2187            body.splice(
2188                0..0,
2189                early_hoisted_stmts
2190                    .into_values()
2191                    .chain(hoisted_stmts.into_values())
2192                    .map(ModuleItem::Stmt),
2193            );
2194            body.extend(
2195                early_late_stmts
2196                    .into_values()
2197                    .chain(late_stmts.into_values())
2198                    .map(ModuleItem::Stmt),
2199            );
2200        }
2201        Program::Script(Script { body, .. }) => {
2202            body.splice(
2203                0..0,
2204                early_hoisted_stmts
2205                    .into_values()
2206                    .chain(hoisted_stmts.into_values()),
2207            );
2208            body.extend(
2209                early_late_stmts
2210                    .into_values()
2211                    .chain(late_stmts.into_values()),
2212            );
2213        }
2214    };
2215}
2216
2217/// Like `hygiene`, but only renames the Atoms without clearing all SyntaxContexts
2218///
2219/// Don't rename idents marked with `is_import_mark` (i.e. a reference to a value which is imported
2220/// from another merged module) or listed in `preserve_exports` (i.e. an exported local binding):
2221/// even if they are causing collisions, they will be handled by the next hygiene pass over the
2222/// whole module.
2223fn hygiene_rename_only(
2224    top_level_mark: Option<Mark>,
2225    is_import_mark: Mark,
2226    preserved_exports: &FxHashSet<Id>,
2227) -> impl VisitMut {
2228    struct HygieneRenamer<'a> {
2229        preserved_exports: &'a FxHashSet<Id>,
2230        is_import_mark: Mark,
2231    }
2232    // Copied from `hygiene_with_config`'s HygieneRenamer, but added an `preserved_exports`
2233    impl swc_core::ecma::transforms::base::rename::Renamer for HygieneRenamer<'_> {
2234        type Target = Id;
2235
2236        const MANGLE: bool = false;
2237        const RESET_N: bool = true;
2238
2239        fn new_name_for(&self, orig: &Id, n: &mut usize) -> Atom {
2240            let res = if *n == 0 {
2241                orig.0.clone()
2242            } else {
2243                format!("{}{}", orig.0, n).into()
2244            };
2245            *n += 1;
2246            res
2247        }
2248
2249        fn preserve_name(&self, orig: &Id) -> bool {
2250            self.preserved_exports.contains(orig) || orig.1.has_mark(self.is_import_mark)
2251        }
2252    }
2253    swc_core::ecma::transforms::base::rename::renamer_keep_contexts(
2254        swc_core::ecma::transforms::base::hygiene::Config {
2255            top_level_mark: top_level_mark.unwrap_or_default(),
2256            ..Default::default()
2257        },
2258        HygieneRenamer {
2259            preserved_exports,
2260            is_import_mark,
2261        },
2262    )
2263}
2264
2265#[derive(Default)]
2266enum CodeGenResultSourceMap {
2267    #[default]
2268    /// No source map should be generated for this module
2269    None,
2270    Single {
2271        source_map: Arc<SourceMap>,
2272    },
2273    ScopeHoisting {
2274        /// The bitwidth of the modules header in the spans, see
2275        /// [CodeGenResultComments::encode_bytepos]
2276        modules_header_width: u32,
2277        lookup_table: Arc<Mutex<Vec<ModulePosition>>>,
2278        source_maps: Vec<CodeGenResultSourceMap>,
2279    },
2280}
2281
2282impl CodeGenResultSourceMap {
2283    fn is_some(&self) -> bool {
2284        match self {
2285            CodeGenResultSourceMap::None => false,
2286            CodeGenResultSourceMap::Single { .. }
2287            | CodeGenResultSourceMap::ScopeHoisting { .. } => true,
2288        }
2289    }
2290}
2291
2292impl Debug for CodeGenResultSourceMap {
2293    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2294        match self {
2295            CodeGenResultSourceMap::None => write!(f, "CodeGenResultSourceMap::None"),
2296            CodeGenResultSourceMap::Single { source_map } => {
2297                write!(
2298                    f,
2299                    "CodeGenResultSourceMap::Single {{ source_map: {:?} }}",
2300                    source_map.files().clone()
2301                )
2302            }
2303            CodeGenResultSourceMap::ScopeHoisting {
2304                modules_header_width,
2305                source_maps,
2306                ..
2307            } => write!(
2308                f,
2309                "CodeGenResultSourceMap::ScopeHoisting {{ modules_header_width: \
2310                 {modules_header_width}, source_maps: {source_maps:?} }}",
2311            ),
2312        }
2313    }
2314}
2315
2316impl Files for CodeGenResultSourceMap {
2317    fn try_lookup_source_file(
2318        &self,
2319        pos: BytePos,
2320    ) -> Result<Option<Arc<SourceFile>>, SourceMapLookupError> {
2321        match self {
2322            CodeGenResultSourceMap::None => Ok(None),
2323            CodeGenResultSourceMap::Single { source_map } => source_map.try_lookup_source_file(pos),
2324            CodeGenResultSourceMap::ScopeHoisting {
2325                modules_header_width,
2326                lookup_table,
2327                source_maps,
2328            } => {
2329                let (module, pos) =
2330                    CodeGenResultComments::decode_bytepos(*modules_header_width, pos, lookup_table);
2331                source_maps[module].try_lookup_source_file(pos)
2332            }
2333        }
2334    }
2335
2336    fn is_in_file(&self, f: &Arc<SourceFile>, raw_pos: BytePos) -> bool {
2337        match self {
2338            CodeGenResultSourceMap::None => false,
2339            CodeGenResultSourceMap::Single { .. } => f.start_pos <= raw_pos && raw_pos < f.end_pos,
2340            CodeGenResultSourceMap::ScopeHoisting { .. } => {
2341                // let (module, pos) = CodeGenResultComments::decode_bytepos(*modules_header_width,
2342                // pos);
2343
2344                // TODO optimize this, unfortunately, `SourceFile` doesn't know which `module` it
2345                // belongs from.
2346                false
2347            }
2348        }
2349    }
2350
2351    fn map_raw_pos(&self, pos: BytePos) -> BytePos {
2352        match self {
2353            CodeGenResultSourceMap::None => BytePos::DUMMY,
2354            CodeGenResultSourceMap::Single { .. } => pos,
2355            CodeGenResultSourceMap::ScopeHoisting {
2356                modules_header_width,
2357                lookup_table,
2358                ..
2359            } => CodeGenResultComments::decode_bytepos(*modules_header_width, pos, lookup_table).1,
2360        }
2361    }
2362}
2363
2364impl SourceMapper for CodeGenResultSourceMap {
2365    fn lookup_char_pos(&self, pos: BytePos) -> Loc {
2366        match self {
2367            CodeGenResultSourceMap::None => {
2368                panic!("CodeGenResultSourceMap::None cannot lookup_char_pos")
2369            }
2370            CodeGenResultSourceMap::Single { source_map } => source_map.lookup_char_pos(pos),
2371            CodeGenResultSourceMap::ScopeHoisting {
2372                modules_header_width,
2373                lookup_table,
2374                source_maps,
2375            } => {
2376                let (module, pos) =
2377                    CodeGenResultComments::decode_bytepos(*modules_header_width, pos, lookup_table);
2378                source_maps[module].lookup_char_pos(pos)
2379            }
2380        }
2381    }
2382    fn span_to_lines(&self, sp: Span) -> FileLinesResult {
2383        match self {
2384            CodeGenResultSourceMap::None => {
2385                panic!("CodeGenResultSourceMap::None cannot span_to_lines")
2386            }
2387            CodeGenResultSourceMap::Single { source_map } => source_map.span_to_lines(sp),
2388            CodeGenResultSourceMap::ScopeHoisting {
2389                modules_header_width,
2390                lookup_table,
2391                source_maps,
2392            } => {
2393                let (module, lo) = CodeGenResultComments::decode_bytepos(
2394                    *modules_header_width,
2395                    sp.lo,
2396                    lookup_table,
2397                );
2398                source_maps[module].span_to_lines(Span {
2399                    lo,
2400                    hi: CodeGenResultComments::decode_bytepos(
2401                        *modules_header_width,
2402                        sp.hi,
2403                        lookup_table,
2404                    )
2405                    .1,
2406                })
2407            }
2408        }
2409    }
2410    fn span_to_string(&self, sp: Span) -> String {
2411        match self {
2412            CodeGenResultSourceMap::None => {
2413                panic!("CodeGenResultSourceMap::None cannot span_to_string")
2414            }
2415            CodeGenResultSourceMap::Single { source_map } => source_map.span_to_string(sp),
2416            CodeGenResultSourceMap::ScopeHoisting {
2417                modules_header_width,
2418                lookup_table,
2419                source_maps,
2420            } => {
2421                let (module, lo) = CodeGenResultComments::decode_bytepos(
2422                    *modules_header_width,
2423                    sp.lo,
2424                    lookup_table,
2425                );
2426                source_maps[module].span_to_string(Span {
2427                    lo,
2428                    hi: CodeGenResultComments::decode_bytepos(
2429                        *modules_header_width,
2430                        sp.hi,
2431                        lookup_table,
2432                    )
2433                    .1,
2434                })
2435            }
2436        }
2437    }
2438    fn span_to_filename(&self, sp: Span) -> Arc<FileName> {
2439        match self {
2440            CodeGenResultSourceMap::None => {
2441                panic!("CodeGenResultSourceMap::None cannot span_to_filename")
2442            }
2443            CodeGenResultSourceMap::Single { source_map } => source_map.span_to_filename(sp),
2444            CodeGenResultSourceMap::ScopeHoisting {
2445                modules_header_width,
2446                lookup_table,
2447                source_maps,
2448            } => {
2449                let (module, lo) = CodeGenResultComments::decode_bytepos(
2450                    *modules_header_width,
2451                    sp.lo,
2452                    lookup_table,
2453                );
2454                source_maps[module].span_to_filename(Span {
2455                    lo,
2456                    hi: CodeGenResultComments::decode_bytepos(
2457                        *modules_header_width,
2458                        sp.hi,
2459                        lookup_table,
2460                    )
2461                    .1,
2462                })
2463            }
2464        }
2465    }
2466    fn merge_spans(&self, sp_lhs: Span, sp_rhs: Span) -> Option<Span> {
2467        match self {
2468            CodeGenResultSourceMap::None => {
2469                panic!("CodeGenResultSourceMap::None cannot merge_spans")
2470            }
2471            CodeGenResultSourceMap::Single { source_map } => source_map.merge_spans(sp_lhs, sp_rhs),
2472            CodeGenResultSourceMap::ScopeHoisting {
2473                modules_header_width,
2474                lookup_table,
2475                source_maps,
2476            } => {
2477                let (module_lhs, lo_lhs) = CodeGenResultComments::decode_bytepos(
2478                    *modules_header_width,
2479                    sp_lhs.lo,
2480                    lookup_table,
2481                );
2482                let (module_rhs, lo_rhs) = CodeGenResultComments::decode_bytepos(
2483                    *modules_header_width,
2484                    sp_rhs.lo,
2485                    lookup_table,
2486                );
2487                if module_lhs != module_rhs {
2488                    return None;
2489                }
2490                source_maps[module_lhs].merge_spans(
2491                    Span {
2492                        lo: lo_lhs,
2493                        hi: CodeGenResultComments::decode_bytepos(
2494                            *modules_header_width,
2495                            sp_lhs.hi,
2496                            lookup_table,
2497                        )
2498                        .1,
2499                    },
2500                    Span {
2501                        lo: lo_rhs,
2502                        hi: CodeGenResultComments::decode_bytepos(
2503                            *modules_header_width,
2504                            sp_rhs.hi,
2505                            lookup_table,
2506                        )
2507                        .1,
2508                    },
2509                )
2510            }
2511        }
2512    }
2513    fn call_span_if_macro(&self, sp: Span) -> Span {
2514        match self {
2515            CodeGenResultSourceMap::None => {
2516                panic!("CodeGenResultSourceMap::None cannot call_span_if_macro")
2517            }
2518            CodeGenResultSourceMap::Single { source_map } => source_map.call_span_if_macro(sp),
2519            CodeGenResultSourceMap::ScopeHoisting {
2520                modules_header_width,
2521                lookup_table,
2522                source_maps,
2523            } => {
2524                let (module, lo) = CodeGenResultComments::decode_bytepos(
2525                    *modules_header_width,
2526                    sp.lo,
2527                    lookup_table,
2528                );
2529                source_maps[module].call_span_if_macro(Span {
2530                    lo,
2531                    hi: CodeGenResultComments::decode_bytepos(
2532                        *modules_header_width,
2533                        sp.hi,
2534                        lookup_table,
2535                    )
2536                    .1,
2537                })
2538            }
2539        }
2540    }
2541    fn doctest_offset_line(&self, _line: usize) -> usize {
2542        panic!("doctest_offset_line is not implemented for CodeGenResultSourceMap");
2543    }
2544    fn span_to_snippet(&self, sp: Span) -> Result<String, Box<SpanSnippetError>> {
2545        match self {
2546            CodeGenResultSourceMap::None => Err(Box::new(SpanSnippetError::SourceNotAvailable {
2547                filename: FileName::Anon,
2548            })),
2549            CodeGenResultSourceMap::Single { source_map } => source_map.span_to_snippet(sp),
2550            CodeGenResultSourceMap::ScopeHoisting {
2551                modules_header_width,
2552                lookup_table,
2553                source_maps,
2554            } => {
2555                let (module, lo) = CodeGenResultComments::decode_bytepos(
2556                    *modules_header_width,
2557                    sp.lo,
2558                    lookup_table,
2559                );
2560                source_maps[module].span_to_snippet(Span {
2561                    lo,
2562                    hi: CodeGenResultComments::decode_bytepos(
2563                        *modules_header_width,
2564                        sp.hi,
2565                        lookup_table,
2566                    )
2567                    .1,
2568                })
2569            }
2570        }
2571    }
2572}
2573impl SourceMapperExt for CodeGenResultSourceMap {
2574    fn get_code_map(&self) -> &dyn SourceMapper {
2575        self
2576    }
2577}
2578
2579#[derive(Debug)]
2580enum CodeGenResultOriginalSourceMap {
2581    Single(Option<ResolvedVc<Box<dyn GenerateSourceMap>>>),
2582    ScopeHoisting(SmallVec<[ResolvedVc<Box<dyn GenerateSourceMap>>; 1]>),
2583}
2584
2585impl CodeGenResultOriginalSourceMap {
2586    fn iter(&self) -> impl Iterator<Item = ResolvedVc<Box<dyn GenerateSourceMap>>> {
2587        match self {
2588            CodeGenResultOriginalSourceMap::Single(map) => Either::Left(map.iter().copied()),
2589            CodeGenResultOriginalSourceMap::ScopeHoisting(maps) => {
2590                Either::Right(maps.iter().copied())
2591            }
2592        }
2593    }
2594}
2595
2596/// Stores a module index in position 0 and the full byte position of the source map in position 1
2597struct ModulePosition(u32, u32);
2598
2599enum CodeGenResultComments {
2600    Single {
2601        comments: Either<ImmutableComments, Arc<ImmutableComments>>,
2602        extra_comments: SwcComments,
2603    },
2604    ScopeHoisting {
2605        /// The bitwidth of the modules header in the spans, see
2606        /// [CodeGenResultComments::encode_bytepos]
2607        modules_header_width: u32,
2608        lookup_table: Arc<Mutex<Vec<ModulePosition>>>,
2609        comments: Vec<CodeGenResultComments>,
2610    },
2611    Empty,
2612}
2613
2614unsafe impl Send for CodeGenResultComments {}
2615unsafe impl Sync for CodeGenResultComments {}
2616
2617impl CodeGenResultComments {
2618    const CONTINUATION_BIT: u32 = 1 << 31;
2619    const SIGN_EXTENSION_BIT: u32 = 1 << 30;
2620
2621    #[inline]
2622    fn encode_bytepos_impl(
2623        modules_header_width: u32,
2624        module: u32,
2625        pos: BytePos,
2626        push_into_lookup: &mut impl FnMut(u32, u32) -> Result<u32>,
2627    ) -> Result<BytePos> {
2628        if pos.is_dummy() {
2629            // nothing to encode
2630            return Ok(pos);
2631        }
2632
2633        // Bit layout for encoded BytePos (32 bits):
2634        // [31] Continuation bit. If set (1), the remaining 31 bits [0..30] encode an index into
2635        //      the lookup vector where (module, original_bytepos) is stored.
2636        //      In this case, decoding ignores other fields and fetches from the table.
2637        // If not set (0):
2638        // [30] Sign-extend bit. Indicates whether the stolen high bits of the original bytepos
2639        //      were all 1s (1) or all 0s (0), so that decoding can restore the original high bits.
2640        // [30 - modules_header_width + 1 .. 30) Module id: modules_header_width bits immediately
2641        //      below the sign-extend bit.
2642        // [0 .. (32 - (2 + modules_header_width)) ) Remaining low bits store the truncated bytepos.
2643        //
2644        // Notes:
2645        // - We reserve 2 header bits always (continuation + sign-extend), so header_width =
2646        //   modules_header_width + 2, and pos_width = 32 - header_width.
2647        // - When the original value does not fit in the available pos_width with a uniform high bit
2648        //   pattern, we spill (set continuation) and store (module, pos) in the lookup table and
2649        //   encode the index with the continuation bit set.
2650        //
2651        // Example (diagrammatic only):
2652        // modules_header_width = 4
2653        // Key:
2654        // (c = continuation, s = sign-extend, m = module, p = pos bits, i = lookup table index)
2655        //
2656        // The continuation bit is set, and the remaining 31 bits are reinterpreted as the index
2657        // into the lookup table.
2658        // Bytes: 1iii iiii iiii iiii iiii iiii iiii iiii
2659        //
2660        // The continuation bit is not set,
2661        // Bytes: 0smm mmpp pppp pppp pppp pppp pppp pppp
2662
2663        let header_width = modules_header_width + 2;
2664        let pos_width = 32 - header_width;
2665
2666        let pos = pos.0;
2667
2668        let old_high_bits = pos >> pos_width;
2669        let high_bits_set = if (2u32.pow(header_width) - 1) == old_high_bits {
2670            true
2671        } else if old_high_bits == 0 {
2672            false
2673        } else {
2674            // The integer is too large for our desired header width and we need to store the result
2675            // in our vector and set the flag to reinterpret this data as the index of
2676            // the vector where the element is being stored.
2677            let ix = push_into_lookup(module, pos)?;
2678            // Make sure that the index fits within the allotted bits
2679            assert_eq!(ix & CodeGenResultComments::CONTINUATION_BIT, 0);
2680
2681            return Ok(BytePos(ix | CodeGenResultComments::CONTINUATION_BIT));
2682        };
2683
2684        let pos = pos & !((2u32.pow(header_width) - 1) << pos_width);
2685        let encoded_high_bits = if high_bits_set {
2686            CodeGenResultComments::SIGN_EXTENSION_BIT
2687        } else {
2688            0
2689        };
2690        let encoded_module = module << pos_width;
2691
2692        Ok(BytePos(encoded_module | encoded_high_bits | pos))
2693    }
2694
2695    fn take(&mut self) -> Self {
2696        std::mem::replace(self, CodeGenResultComments::Empty)
2697    }
2698
2699    fn consumable(&self) -> CodeGenResultCommentsConsumable<'_> {
2700        match self {
2701            CodeGenResultComments::Single {
2702                comments,
2703                extra_comments,
2704            } => CodeGenResultCommentsConsumable::Single {
2705                comments: match comments {
2706                    Either::Left(comments) => comments.consumable(),
2707                    Either::Right(comments) => comments.consumable(),
2708                },
2709                extra_comments,
2710            },
2711            CodeGenResultComments::ScopeHoisting {
2712                modules_header_width,
2713                lookup_table,
2714                comments,
2715            } => CodeGenResultCommentsConsumable::ScopeHoisting {
2716                modules_header_width: *modules_header_width,
2717                lookup_table: lookup_table.clone(),
2718                comments: comments.iter().map(|c| c.consumable()).collect(),
2719            },
2720            CodeGenResultComments::Empty => CodeGenResultCommentsConsumable::Empty,
2721        }
2722    }
2723
2724    fn encode_bytepos(
2725        modules_header_width: u32,
2726        module: u32,
2727        pos: BytePos,
2728        lookup_table: Arc<Mutex<Vec<ModulePosition>>>,
2729    ) -> Result<BytePos> {
2730        let mut push = |module: u32, pos_u32: u32| -> Result<u32> {
2731            let mut lookup_table = lookup_table
2732                .lock()
2733                .map_err(|_| anyhow!("Failed to grab lock on the index map for byte positions"))?;
2734            let ix = lookup_table.len() as u32;
2735            if ix >= 1 << 30 {
2736                bail!("Too many byte positions being stored");
2737            }
2738            lookup_table.push(ModulePosition(module, pos_u32));
2739            Ok(ix)
2740        };
2741        Self::encode_bytepos_impl(modules_header_width, module, pos, &mut push)
2742    }
2743
2744    fn encode_bytepos_with_vec(
2745        modules_header_width: u32,
2746        module: u32,
2747        pos: BytePos,
2748        lookup_table: &mut Vec<ModulePosition>,
2749    ) -> Result<BytePos> {
2750        let mut push = |module: u32, pos_u32: u32| -> Result<u32> {
2751            let ix = lookup_table.len() as u32;
2752            if ix >= 1 << 30 {
2753                bail!("Too many byte positions being stored");
2754            }
2755            lookup_table.push(ModulePosition(module, pos_u32));
2756            Ok(ix)
2757        };
2758        Self::encode_bytepos_impl(modules_header_width, module, pos, &mut push)
2759    }
2760
2761    fn decode_bytepos(
2762        modules_header_width: u32,
2763        pos: BytePos,
2764        lookup_table: &Mutex<Vec<ModulePosition>>,
2765    ) -> (usize, BytePos) {
2766        if pos.is_dummy() {
2767            // nothing to decode
2768            panic!("Cannot decode dummy BytePos");
2769        }
2770
2771        let header_width = modules_header_width + 2;
2772        let pos_width = 32 - header_width;
2773
2774        if (CodeGenResultComments::CONTINUATION_BIT & pos.0)
2775            == CodeGenResultComments::CONTINUATION_BIT
2776        {
2777            let lookup_table = lookup_table
2778                .lock()
2779                .expect("Failed to grab lock on the index map for byte position");
2780            let ix = pos.0 & !CodeGenResultComments::CONTINUATION_BIT;
2781            let ModulePosition(module, pos) = lookup_table[ix as usize];
2782
2783            return (module as usize, BytePos(pos));
2784        }
2785
2786        let high_bits_set = pos.0 >> 30 & 1 == 1;
2787        let module = (pos.0 << 2) >> (pos_width + 2);
2788        let pos = pos.0 & !((2u32.pow(header_width) - 1) << pos_width);
2789        let pos = if high_bits_set {
2790            pos | ((2u32.pow(header_width) - 1) << pos_width)
2791        } else {
2792            pos
2793        };
2794        (module as usize, BytePos(pos))
2795    }
2796}
2797
2798enum CodeGenResultCommentsConsumable<'a> {
2799    Single {
2800        comments: CowComments<'a>,
2801        extra_comments: &'a SwcComments,
2802    },
2803    ScopeHoisting {
2804        modules_header_width: u32,
2805        lookup_table: Arc<Mutex<Vec<ModulePosition>>>,
2806        comments: Vec<CodeGenResultCommentsConsumable<'a>>,
2807    },
2808    Empty,
2809}
2810/// All BytePos in Spans in the AST are encoded correctly in [`merge_modules`], but the Comments
2811/// also contain spans. These also need to be encoded so that all pos in `mappings` are consistently
2812/// encoded.
2813fn encode_module_into_comment_span(
2814    modules_header_width: u32,
2815    module: usize,
2816    mut comment: Comment,
2817    lookup_table: Arc<Mutex<Vec<ModulePosition>>>,
2818) -> Comment {
2819    comment.span.lo = CodeGenResultComments::encode_bytepos(
2820        modules_header_width,
2821        module as u32,
2822        comment.span.lo,
2823        lookup_table.clone(),
2824    )
2825    .unwrap();
2826    comment.span.hi = CodeGenResultComments::encode_bytepos(
2827        modules_header_width,
2828        module as u32,
2829        comment.span.hi,
2830        lookup_table,
2831    )
2832    .unwrap();
2833    comment
2834}
2835
2836impl Comments for CodeGenResultCommentsConsumable<'_> {
2837    fn add_leading(&self, _pos: BytePos, _cmt: Comment) {
2838        unimplemented!("add_leading")
2839    }
2840
2841    fn add_leading_comments(&self, _pos: BytePos, _comments: Vec<Comment>) {
2842        unimplemented!("add_leading_comments")
2843    }
2844
2845    fn has_leading(&self, pos: BytePos) -> bool {
2846        if pos.is_dummy() {
2847            return false;
2848        }
2849        match self {
2850            Self::Single {
2851                comments,
2852                extra_comments,
2853            } => comments.has_leading(pos) || extra_comments.has_leading(pos),
2854            Self::ScopeHoisting {
2855                modules_header_width,
2856                lookup_table,
2857                comments,
2858            } => {
2859                let (module, pos) =
2860                    CodeGenResultComments::decode_bytepos(*modules_header_width, pos, lookup_table);
2861                comments[module].has_leading(pos)
2862            }
2863            Self::Empty => false,
2864        }
2865    }
2866
2867    fn move_leading(&self, _from: BytePos, _to: BytePos) {
2868        unimplemented!("move_leading")
2869    }
2870
2871    fn take_leading(&self, pos: BytePos) -> Option<Vec<Comment>> {
2872        if pos.is_dummy() {
2873            return None;
2874        }
2875        match self {
2876            Self::Single {
2877                comments,
2878                extra_comments,
2879            } => merge_option_vec(comments.take_leading(pos), extra_comments.take_leading(pos)),
2880            Self::ScopeHoisting {
2881                modules_header_width,
2882                lookup_table,
2883                comments,
2884            } => {
2885                let (module, pos) =
2886                    CodeGenResultComments::decode_bytepos(*modules_header_width, pos, lookup_table);
2887                comments[module].take_leading(pos).map(|comments| {
2888                    comments
2889                        .into_iter()
2890                        .map(|c| {
2891                            encode_module_into_comment_span(
2892                                *modules_header_width,
2893                                module,
2894                                c,
2895                                lookup_table.clone(),
2896                            )
2897                        })
2898                        .collect()
2899                })
2900            }
2901            Self::Empty => None,
2902        }
2903    }
2904
2905    fn get_leading(&self, pos: BytePos) -> Option<Vec<Comment>> {
2906        if pos.is_dummy() {
2907            return None;
2908        }
2909        match self {
2910            Self::Single {
2911                comments,
2912                extra_comments,
2913            } => merge_option_vec(comments.get_leading(pos), extra_comments.get_leading(pos)),
2914            Self::ScopeHoisting {
2915                modules_header_width,
2916                lookup_table,
2917                comments,
2918            } => {
2919                let (module, pos) =
2920                    CodeGenResultComments::decode_bytepos(*modules_header_width, pos, lookup_table);
2921                comments[module].get_leading(pos).map(|comments| {
2922                    comments
2923                        .into_iter()
2924                        .map(|c| {
2925                            encode_module_into_comment_span(
2926                                *modules_header_width,
2927                                module,
2928                                c,
2929                                lookup_table.clone(),
2930                            )
2931                        })
2932                        .collect()
2933                })
2934            }
2935            Self::Empty => None,
2936        }
2937    }
2938
2939    fn add_trailing(&self, _pos: BytePos, _cmt: Comment) {
2940        unimplemented!("add_trailing")
2941    }
2942
2943    fn add_trailing_comments(&self, _pos: BytePos, _comments: Vec<Comment>) {
2944        unimplemented!("add_trailing_comments")
2945    }
2946
2947    fn has_trailing(&self, pos: BytePos) -> bool {
2948        if pos.is_dummy() {
2949            return false;
2950        }
2951        match self {
2952            Self::Single {
2953                comments,
2954                extra_comments,
2955            } => comments.has_trailing(pos) || extra_comments.has_trailing(pos),
2956            Self::ScopeHoisting {
2957                modules_header_width,
2958                lookup_table,
2959                comments,
2960            } => {
2961                let (module, pos) =
2962                    CodeGenResultComments::decode_bytepos(*modules_header_width, pos, lookup_table);
2963                comments[module].has_trailing(pos)
2964            }
2965            Self::Empty => false,
2966        }
2967    }
2968
2969    fn move_trailing(&self, _from: BytePos, _to: BytePos) {
2970        unimplemented!("move_trailing")
2971    }
2972
2973    fn take_trailing(&self, pos: BytePos) -> Option<Vec<Comment>> {
2974        if pos.is_dummy() {
2975            return None;
2976        }
2977        match self {
2978            Self::Single {
2979                comments,
2980                extra_comments,
2981            } => merge_option_vec(
2982                comments.take_trailing(pos),
2983                extra_comments.take_trailing(pos),
2984            ),
2985            Self::ScopeHoisting {
2986                modules_header_width,
2987                lookup_table,
2988                comments,
2989            } => {
2990                let (module, pos) =
2991                    CodeGenResultComments::decode_bytepos(*modules_header_width, pos, lookup_table);
2992                comments[module].take_trailing(pos).map(|comments| {
2993                    comments
2994                        .into_iter()
2995                        .map(|c| {
2996                            encode_module_into_comment_span(
2997                                *modules_header_width,
2998                                module,
2999                                c,
3000                                lookup_table.clone(),
3001                            )
3002                        })
3003                        .collect()
3004                })
3005            }
3006            Self::Empty => None,
3007        }
3008    }
3009
3010    fn get_trailing(&self, pos: BytePos) -> Option<Vec<Comment>> {
3011        if pos.is_dummy() {
3012            return None;
3013        }
3014        match self {
3015            Self::Single {
3016                comments,
3017                extra_comments,
3018            } => merge_option_vec(comments.get_leading(pos), extra_comments.get_leading(pos)),
3019            Self::ScopeHoisting {
3020                modules_header_width,
3021                lookup_table,
3022                comments,
3023            } => {
3024                let (module, pos) =
3025                    CodeGenResultComments::decode_bytepos(*modules_header_width, pos, lookup_table);
3026                comments[module].get_leading(pos).map(|comments| {
3027                    comments
3028                        .into_iter()
3029                        .map(|c| {
3030                            encode_module_into_comment_span(
3031                                *modules_header_width,
3032                                module,
3033                                c,
3034                                lookup_table.clone(),
3035                            )
3036                        })
3037                        .collect()
3038                })
3039            }
3040            Self::Empty => None,
3041        }
3042    }
3043
3044    fn add_pure_comment(&self, _pos: BytePos) {
3045        unimplemented!("add_pure_comment")
3046    }
3047}
3048
3049fn merge_option_vec<T>(a: Option<Vec<T>>, b: Option<Vec<T>>) -> Option<Vec<T>> {
3050    match (a, b) {
3051        (Some(a), Some(b)) => Some(a.into_iter().chain(b).collect()),
3052        (Some(a), None) => Some(a),
3053        (None, Some(b)) => Some(b),
3054        (None, None) => None,
3055    }
3056}
3057
3058#[cfg(test)]
3059mod tests {
3060    use super::*;
3061    fn bytepos_ensure_identical(modules_header_width: u32, pos: BytePos) {
3062        let module_count = 2u32.pow(modules_header_width);
3063        let lookup_table = Arc::new(Mutex::new(Vec::new()));
3064
3065        for module in [
3066            0,
3067            1,
3068            2,
3069            module_count / 2,
3070            module_count.wrapping_sub(5),
3071            module_count.wrapping_sub(1),
3072        ]
3073        .into_iter()
3074        .filter(|&m| m < module_count)
3075        {
3076            let encoded = CodeGenResultComments::encode_bytepos(
3077                modules_header_width,
3078                module,
3079                pos,
3080                lookup_table.clone(),
3081            )
3082            .unwrap();
3083            let (decoded_module, decoded_pos) =
3084                CodeGenResultComments::decode_bytepos(modules_header_width, encoded, &lookup_table);
3085            assert_eq!(
3086                decoded_module as u32, module,
3087                "Testing width {modules_header_width} and pos {pos:?}"
3088            );
3089            assert_eq!(
3090                decoded_pos, pos,
3091                "Testing width {modules_header_width} and pos {pos:?}"
3092            );
3093        }
3094    }
3095
3096    #[test]
3097    fn test_encode_decode_bytepos_format() {
3098        let table = Arc::new(Mutex::new(Vec::new()));
3099
3100        for (pos, module, modules_header_width, result) in [
3101            (
3102                0b00000000000000000000000000000101,
3103                0b1,
3104                1,
3105                0b00100000000000000000000000000101,
3106            ),
3107            (
3108                0b00000000000000000000000000000101,
3109                0b01,
3110                2,
3111                0b00010000000000000000000000000101,
3112            ),
3113            (
3114                0b11111111111111110000000000000101,
3115                0b0110,
3116                4,
3117                0b01011011111111110000000000000101,
3118            ),
3119            (
3120                BytePos::PLACEHOLDER.0,
3121                0b01111,
3122                5,
3123                0b01011111111111111111111111111101,
3124            ),
3125            (
3126                BytePos::PURE.0,
3127                0b01111,
3128                5,
3129                0b01011111111111111111111111111110,
3130            ),
3131            (
3132                BytePos::SYNTHESIZED.0,
3133                0b01111,
3134                5,
3135                0b01011111111111111111111111111111,
3136            ),
3137            // This is an index that should trigger the overflow to store the position into the
3138            // lookup table
3139            (
3140                0b00000111111111110000000000000101,
3141                0b0001,
3142                4,
3143                0b10000000000000000000000000000000,
3144            ),
3145            // Another one should increase the index by 1
3146            (
3147                0b00000111111111110000000000111110,
3148                0b0001,
3149                4,
3150                0b10000000000000000000000000000001,
3151            ),
3152            // Special case, DUMMY stays a DUMMY
3153            (BytePos::DUMMY.0, 0b0001, 4, BytePos::DUMMY.0),
3154        ] {
3155            let encoded = CodeGenResultComments::encode_bytepos(
3156                modules_header_width,
3157                module,
3158                BytePos(pos),
3159                table.clone(),
3160            )
3161            .unwrap();
3162            assert_eq!(encoded.0, result);
3163
3164            // Ensure that the correct original module and bytepos are stored when overflow occurs
3165            if encoded.0 & CodeGenResultComments::CONTINUATION_BIT
3166                == CodeGenResultComments::CONTINUATION_BIT
3167            {
3168                let index = encoded.0 & !CodeGenResultComments::CONTINUATION_BIT;
3169                let ModulePosition(encoded_module, encoded_pos) =
3170                    table.lock().unwrap()[index as usize];
3171                assert_eq!(encoded_module, module);
3172                assert_eq!(encoded_pos, pos);
3173            }
3174        }
3175    }
3176
3177    #[test]
3178    fn test_encode_decode_bytepos_lossless() {
3179        // This is copied from swc (it's not exported), comments the range above this value.
3180        const DUMMY_RESERVE: u32 = u32::MAX - 2_u32.pow(16);
3181
3182        for modules_header_width in 1..=10 {
3183            for pos in [
3184                // BytePos::DUMMY, // This must never get decoded in the first place
3185                BytePos(1),
3186                BytePos(2),
3187                BytePos(100),
3188                BytePos(4_000_000),
3189                BytePos(600_000_000),
3190                BytePos(u32::MAX - 3), // The maximum allowed value that isn't reserved by SWC
3191                BytePos::PLACEHOLDER,
3192                BytePos::SYNTHESIZED,
3193                BytePos::PURE,
3194                BytePos(DUMMY_RESERVE),
3195                BytePos(DUMMY_RESERVE + 10),
3196                BytePos(DUMMY_RESERVE + 10000),
3197            ] {
3198                bytepos_ensure_identical(modules_header_width, pos);
3199            }
3200        }
3201    }
3202}