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