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