Skip to main content

turbopack_ecmascript/
lib.rs

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