Skip to main content

turbopack_ecmascript/
lib.rs

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