turbopack_ecmascript/
lib.rs

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