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