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