turbopack_ecmascript/
lib.rs

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