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    if cfg!(debug_assertions) && inserted.len() != contents.len() {
1529        bail!(
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
1540    let comments = contents
1541        .iter_mut()
1542        .map(|(_, content)| content.comments.take())
1543        .collect::<Vec<_>>();
1544
1545    let source_maps = contents
1546        .iter_mut()
1547        .map(|(_, content)| std::mem::take(&mut content.source_map))
1548        .collect::<Vec<_>>();
1549
1550    let original_source_maps = contents
1551        .iter_mut()
1552        .flat_map(|(_, content)| match content.original_source_map {
1553            CodeGenResultOriginalSourceMap::ScopeHoisting(_) => unreachable!(
1554                "Didn't expect nested CodeGenResultOriginalSourceMap::ScopeHoisting: {:?}",
1555                content.original_source_map
1556            ),
1557            CodeGenResultOriginalSourceMap::Single(map) => map,
1558        })
1559        .collect();
1560
1561    Ok((
1562        merged_ast,
1563        comments,
1564        source_maps,
1565        original_source_maps,
1566        Arc::new(Mutex::new(lookup_table)),
1567    ))
1568}
1569
1570/// Provides information about the other modules in the current scope hoisting group.
1571///
1572/// Note that this object contains interior mutability to lazily create syntax contexts in
1573/// `get_module_syntax_context`.
1574#[derive(Clone, Copy)]
1575pub enum ScopeHoistingContext<'a> {
1576    Some {
1577        /// The current module when scope hoisting
1578        module: ResolvedVc<Box<dyn EcmascriptChunkPlaceable>>,
1579        /// All modules in the current group, and whether they should expose their exports
1580        modules:
1581            &'a FxIndexMap<ResolvedVc<Box<dyn EcmascriptChunkPlaceable>>, MergeableModuleExposure>,
1582
1583        is_import_mark: Mark,
1584        globals: &'a Arc<Globals>,
1585        // Interior mutability!
1586        module_syntax_contexts_cache:
1587            &'a FxDashMap<ResolvedVc<Box<dyn EcmascriptChunkPlaceable>>, SyntaxContext>,
1588    },
1589    None,
1590}
1591
1592impl<'a> ScopeHoistingContext<'a> {
1593    /// The current module when scope hoisting
1594    pub fn module(&self) -> Option<ResolvedVc<Box<dyn EcmascriptChunkPlaceable>>> {
1595        match self {
1596            ScopeHoistingContext::Some { module, .. } => Some(*module),
1597            ScopeHoistingContext::None => None,
1598        }
1599    }
1600
1601    /// Whether the current module should not expose it's exports into the module cache.
1602    pub fn skip_module_exports(&self) -> bool {
1603        match self {
1604            ScopeHoistingContext::Some {
1605                module, modules, ..
1606            } => match modules.get(module).unwrap() {
1607                MergeableModuleExposure::None => true,
1608                MergeableModuleExposure::Internal | MergeableModuleExposure::External => false,
1609            },
1610            ScopeHoistingContext::None => false,
1611        }
1612    }
1613
1614    /// To import a specifier from another module, apply this context to the Ident
1615    pub fn get_module_syntax_context(
1616        &self,
1617        module: ResolvedVc<Box<dyn EcmascriptChunkPlaceable>>,
1618    ) -> Option<SyntaxContext> {
1619        match self {
1620            ScopeHoistingContext::Some {
1621                modules,
1622                module_syntax_contexts_cache,
1623                globals,
1624                is_import_mark,
1625                ..
1626            } => {
1627                if !modules.contains_key(&module) {
1628                    return None;
1629                }
1630
1631                Some(match module_syntax_contexts_cache.entry(module) {
1632                    dashmap::Entry::Occupied(e) => *e.get(),
1633                    dashmap::Entry::Vacant(e) => {
1634                        let ctxt = GLOBALS.set(globals, || {
1635                            let mark = Mark::fresh(*is_import_mark);
1636                            SyntaxContext::empty()
1637                                .apply_mark(*is_import_mark)
1638                                .apply_mark(mark)
1639                        });
1640
1641                        e.insert(ctxt);
1642                        ctxt
1643                    }
1644                })
1645            }
1646            ScopeHoistingContext::None => None,
1647        }
1648    }
1649
1650    pub fn get_module_index(
1651        &self,
1652        module: ResolvedVc<Box<dyn EcmascriptChunkPlaceable>>,
1653    ) -> Option<usize> {
1654        match self {
1655            ScopeHoistingContext::Some { modules, .. } => modules.get_index_of(&module),
1656            ScopeHoistingContext::None => None,
1657        }
1658    }
1659}
1660
1661struct CodeGenResult {
1662    program: Program,
1663    source_map: CodeGenResultSourceMap,
1664    comments: CodeGenResultComments,
1665    is_esm: bool,
1666    strict: bool,
1667    original_source_map: CodeGenResultOriginalSourceMap,
1668    minify: MinifyType,
1669    #[allow(clippy::type_complexity)]
1670    /// (Map<Module, corresponding context for imports>, `eval_context.imports.exports`)
1671    scope_hoisting_syntax_contexts: Option<(
1672        FxDashMap<ResolvedVc<Box<dyn EcmascriptChunkPlaceable + 'static>>, SyntaxContext>,
1673        FxHashMap<RcStr, Id>,
1674    )>,
1675}
1676
1677struct ScopeHoistingOptions<'a> {
1678    module: ResolvedVc<Box<dyn EcmascriptChunkPlaceable>>,
1679    modules: &'a FxIndexMap<ResolvedVc<Box<dyn EcmascriptChunkPlaceable>>, MergeableModuleExposure>,
1680}
1681
1682async fn process_parse_result(
1683    parsed: Option<ResolvedVc<ParseResult>>,
1684    ident: Vc<AssetIdent>,
1685    specified_module_type: SpecifiedModuleType,
1686    generate_source_map: bool,
1687    original_source_map: Option<ResolvedVc<Box<dyn GenerateSourceMap>>>,
1688    minify: MinifyType,
1689    options: Option<&EcmascriptModuleContentOptions>,
1690    scope_hoisting_options: Option<ScopeHoistingOptions<'_>>,
1691) -> Result<CodeGenResult> {
1692    with_consumed_parse_result(
1693        parsed,
1694        async |mut program, source_map, globals, eval_context, comments| -> Result<CodeGenResult> {
1695            let (top_level_mark, is_esm, strict) = eval_context
1696                .as_ref()
1697                .map_either(
1698                    |e| {
1699                        (
1700                            e.top_level_mark,
1701                            e.is_esm(specified_module_type),
1702                            e.imports.strict,
1703                        )
1704                    },
1705                    |e| {
1706                        (
1707                            e.top_level_mark,
1708                            e.is_esm(specified_module_type),
1709                            e.imports.strict,
1710                        )
1711                    },
1712                )
1713                .into_inner();
1714
1715            let (mut code_gens, retain_syntax_context, prepend_ident_comment) =
1716                if let Some(scope_hoisting_options) = scope_hoisting_options {
1717                    let is_import_mark = GLOBALS.set(globals, || Mark::new());
1718
1719                    let module_syntax_contexts_cache = FxDashMap::default();
1720                    let ctx = ScopeHoistingContext::Some {
1721                        module: scope_hoisting_options.module,
1722                        modules: scope_hoisting_options.modules,
1723                        module_syntax_contexts_cache: &module_syntax_contexts_cache,
1724                        is_import_mark,
1725                        globals,
1726                    };
1727                    let code_gens = options
1728                        .unwrap()
1729                        .merged_code_gens(
1730                            ctx,
1731                            match &eval_context {
1732                                Either::Left(e) => e,
1733                                Either::Right(e) => e,
1734                            },
1735                        )
1736                        .await?;
1737
1738                    let export_contexts = eval_context
1739                        .map_either(
1740                            |e| Cow::Owned(e.imports.exports),
1741                            |e| Cow::Borrowed(&e.imports.exports),
1742                        )
1743                        .into_inner();
1744                    let preserved_exports =
1745                        match &*scope_hoisting_options.module.get_exports().await? {
1746                            EcmascriptExports::EsmExports(exports) => exports
1747                                .await?
1748                                .exports
1749                                .iter()
1750                                .filter(|(_, e)| matches!(e, export::EsmExport::LocalBinding(_, _)))
1751                                .map(|(name, e)| {
1752                                    if let Some((sym, ctxt)) = export_contexts.get(name) {
1753                                        Ok((sym.clone(), *ctxt))
1754                                    } else {
1755                                        bail!("Couldn't find export {} for binding {:?}", name, e);
1756                                    }
1757                                })
1758                                .collect::<Result<FxHashSet<_>>>()?,
1759                            _ => Default::default(),
1760                        };
1761
1762                    let prepend_ident_comment = if matches!(minify, MinifyType::NoMinify) {
1763                        Some(Comment {
1764                            kind: CommentKind::Line,
1765                            span: DUMMY_SP,
1766                            text: (&*turbofmt!(" MERGED MODULE: {}", ident).await?).into(),
1767                        })
1768                    } else {
1769                        None
1770                    };
1771
1772                    (
1773                        code_gens,
1774                        Some((
1775                            is_import_mark,
1776                            module_syntax_contexts_cache,
1777                            preserved_exports,
1778                            export_contexts,
1779                        )),
1780                        prepend_ident_comment,
1781                    )
1782                } else if let Some(options) = options {
1783                    (
1784                        options
1785                            .merged_code_gens(
1786                                ScopeHoistingContext::None,
1787                                match &eval_context {
1788                                    Either::Left(e) => e,
1789                                    Either::Right(e) => e,
1790                                },
1791                            )
1792                            .await?,
1793                        None,
1794                        None,
1795                    )
1796                } else {
1797                    (vec![], None, None)
1798                };
1799
1800            let extra_comments = SwcComments {
1801                leading: Default::default(),
1802                trailing: Default::default(),
1803            };
1804
1805            process_content_with_code_gens(&mut program, globals, &mut code_gens);
1806
1807            for comments in code_gens.iter_mut().flat_map(|cg| cg.comments.as_mut()) {
1808                let leading = Arc::unwrap_or_clone(take(&mut comments.leading));
1809                let trailing = Arc::unwrap_or_clone(take(&mut comments.trailing));
1810
1811                for (pos, v) in leading {
1812                    extra_comments.leading.entry(pos).or_default().extend(v);
1813                }
1814
1815                for (pos, v) in trailing {
1816                    extra_comments.trailing.entry(pos).or_default().extend(v);
1817                }
1818            }
1819
1820            GLOBALS.set(globals, || {
1821                if let Some(prepend_ident_comment) = prepend_ident_comment {
1822                    let span = Span::dummy_with_cmt();
1823                    extra_comments.add_leading(span.lo, prepend_ident_comment);
1824                    let stmt = Stmt::Empty(EmptyStmt { span });
1825                    match &mut program {
1826                        Program::Module(module) => module.body.prepend_stmt(ModuleItem::Stmt(stmt)),
1827                        Program::Script(script) => script.body.prepend_stmt(stmt),
1828                    }
1829                }
1830
1831                if let Some((is_import_mark, _, preserved_exports, _)) = &retain_syntax_context {
1832                    program.visit_mut_with(&mut hygiene_rename_only(
1833                        Some(top_level_mark),
1834                        *is_import_mark,
1835                        preserved_exports,
1836                    ));
1837                } else {
1838                    program.visit_mut_with(
1839                        &mut swc_core::ecma::transforms::base::hygiene::hygiene_with_config(
1840                            swc_core::ecma::transforms::base::hygiene::Config {
1841                                top_level_mark,
1842                                ..Default::default()
1843                            },
1844                        ),
1845                    );
1846                }
1847                program.visit_mut_with(&mut swc_core::ecma::transforms::base::fixer::fixer(None));
1848
1849                // we need to remove any shebang before bundling as it's only valid as the first
1850                // line in a js file (not in a chunk item wrapped in the runtime)
1851                remove_shebang(&mut program);
1852                remove_directives(&mut program);
1853            });
1854
1855            Ok(CodeGenResult {
1856                program,
1857                source_map: if generate_source_map {
1858                    CodeGenResultSourceMap::Single {
1859                        source_map: source_map.clone(),
1860                    }
1861                } else {
1862                    CodeGenResultSourceMap::None
1863                },
1864                comments: CodeGenResultComments::Single {
1865                    comments,
1866                    extra_comments,
1867                },
1868                is_esm,
1869                strict,
1870                original_source_map: CodeGenResultOriginalSourceMap::Single(original_source_map),
1871                minify,
1872                scope_hoisting_syntax_contexts: retain_syntax_context
1873                    // TODO ideally don't clone here
1874                    .map(|(_, ctxts, _, export_contexts)| (ctxts, export_contexts.into_owned())),
1875            })
1876        },
1877        async |parse_result| -> Result<CodeGenResult> {
1878            Ok(match parse_result {
1879                ParseResult::Ok { .. } => unreachable!(),
1880                ParseResult::Unparsable { messages } => {
1881                    let error_messages = messages
1882                        .as_ref()
1883                        .and_then(|m| m.first().map(|f| format!("\n{f}")))
1884                        .unwrap_or("".into());
1885                    let msg = &*turbofmt!(
1886                        "Could not parse module '{}'\n{error_messages}",
1887                        ident.path()
1888                    )
1889                    .await?;
1890                    let body = vec![
1891                        quote!(
1892                            "const e = new Error($msg);" as Stmt,
1893                            msg: Expr = Expr::Lit(msg.into()),
1894                        ),
1895                        quote!("e.code = 'MODULE_UNPARSABLE';" as Stmt),
1896                        quote!("throw e;" as Stmt),
1897                    ];
1898
1899                    CodeGenResult {
1900                        program: Program::Script(Script {
1901                            span: DUMMY_SP,
1902                            body,
1903                            shebang: None,
1904                        }),
1905                        source_map: CodeGenResultSourceMap::None,
1906                        comments: CodeGenResultComments::Empty,
1907                        is_esm: false,
1908                        strict: false,
1909                        original_source_map: CodeGenResultOriginalSourceMap::Single(None),
1910                        minify: MinifyType::NoMinify,
1911                        scope_hoisting_syntax_contexts: None,
1912                    }
1913                }
1914                ParseResult::NotFound => {
1915                    let msg =
1916                        &*turbofmt!("Could not parse module '{}', file not found", ident.path())
1917                            .await?;
1918                    let body = vec![
1919                        quote!(
1920                            "const e = new Error($msg);" as Stmt,
1921                            msg: Expr = Expr::Lit(msg.into()),
1922                        ),
1923                        quote!("e.code = 'MODULE_UNPARSABLE';" as Stmt),
1924                        quote!("throw e;" as Stmt),
1925                    ];
1926                    CodeGenResult {
1927                        program: Program::Script(Script {
1928                            span: DUMMY_SP,
1929                            body,
1930                            shebang: None,
1931                        }),
1932                        source_map: CodeGenResultSourceMap::None,
1933                        comments: CodeGenResultComments::Empty,
1934                        is_esm: false,
1935                        strict: false,
1936                        original_source_map: CodeGenResultOriginalSourceMap::Single(None),
1937                        minify: MinifyType::NoMinify,
1938                        scope_hoisting_syntax_contexts: None,
1939                    }
1940                }
1941            })
1942        },
1943    )
1944    .instrument(tracing::trace_span!(
1945        "process parse result",
1946        ident = display(ident.to_string().await?),
1947    ))
1948    .await
1949}
1950
1951/// Try to avoid cloning the AST and Globals by unwrapping the ReadRef (and cloning otherwise).
1952async fn with_consumed_parse_result<T>(
1953    parsed: Option<ResolvedVc<ParseResult>>,
1954    success: impl AsyncFnOnce(
1955        Program,
1956        &Arc<SourceMap>,
1957        &Arc<Globals>,
1958        Either<EvalContext, &'_ EvalContext>,
1959        Either<ImmutableComments, Arc<ImmutableComments>>,
1960    ) -> Result<T>,
1961    error: impl AsyncFnOnce(&ParseResult) -> Result<T>,
1962) -> Result<T> {
1963    let Some(parsed) = parsed else {
1964        let globals = Globals::new();
1965        let eval_context = GLOBALS.set(&globals, || EvalContext {
1966            unresolved_mark: Mark::new(),
1967            top_level_mark: Mark::new(),
1968            imports: Default::default(),
1969            force_free_values: Default::default(),
1970        });
1971        return success(
1972            Program::Module(swc_core::ecma::ast::Module::dummy()),
1973            &Default::default(),
1974            &Default::default(),
1975            Either::Left(eval_context),
1976            Either::Left(Default::default()),
1977        )
1978        .await;
1979    };
1980
1981    let parsed = parsed.final_read_hint().await?;
1982    match &*parsed {
1983        ParseResult::Ok { .. } => {
1984            let mut parsed = ReadRef::try_unwrap(parsed);
1985            let (program, source_map, globals, eval_context, comments) = match &mut parsed {
1986                Ok(ParseResult::Ok {
1987                    program,
1988                    source_map,
1989                    globals,
1990                    eval_context,
1991                    comments,
1992                    ..
1993                }) => (
1994                    program.take(),
1995                    &*source_map,
1996                    &*globals,
1997                    Either::Left(std::mem::replace(
1998                        eval_context,
1999                        EvalContext {
2000                            unresolved_mark: eval_context.unresolved_mark,
2001                            top_level_mark: eval_context.top_level_mark,
2002                            imports: Default::default(),
2003                            force_free_values: Default::default(),
2004                        },
2005                    )),
2006                    match Arc::try_unwrap(take(comments)) {
2007                        Ok(comments) => Either::Left(comments),
2008                        Err(comments) => Either::Right(comments),
2009                    },
2010                ),
2011                Err(parsed) => {
2012                    let ParseResult::Ok {
2013                        program,
2014                        source_map,
2015                        globals,
2016                        eval_context,
2017                        comments,
2018                        ..
2019                    } = &**parsed
2020                    else {
2021                        unreachable!();
2022                    };
2023                    (
2024                        program.clone(),
2025                        source_map,
2026                        globals,
2027                        Either::Right(eval_context),
2028                        Either::Right(comments.clone()),
2029                    )
2030                }
2031                _ => unreachable!(),
2032            };
2033
2034            success(program, source_map, globals, eval_context, comments).await
2035        }
2036        _ => error(&parsed).await,
2037    }
2038}
2039
2040async fn emit_content(
2041    content: CodeGenResult,
2042    additional_ids: SmallVec<[ModuleId; 1]>,
2043) -> Result<Vc<EcmascriptModuleContent>> {
2044    let CodeGenResult {
2045        program,
2046        source_map,
2047        comments,
2048        is_esm,
2049        strict,
2050        original_source_map,
2051        minify,
2052        scope_hoisting_syntax_contexts: _,
2053    } = content;
2054
2055    let generate_source_map = source_map.is_some();
2056
2057    // Collect identifier names for source maps before emitting
2058    let source_map_names = if generate_source_map {
2059        let mut collector = IdentCollector::default();
2060        program.visit_with(&mut collector);
2061        collector.into_map()
2062    } else {
2063        Default::default()
2064    };
2065
2066    let mut bytes: Vec<u8> = vec![];
2067    // TODO: Insert this as a sourceless segment so that sourcemaps aren't affected.
2068    // = format!("/* {} */\n", self.module.path().to_string().await?).into_bytes();
2069
2070    let mut mappings = vec![];
2071
2072    let source_map = Arc::new(source_map);
2073
2074    {
2075        let mut wr = JsWriter::new(
2076            // unused anyway?
2077            Default::default(),
2078            "\n",
2079            &mut bytes,
2080            generate_source_map.then_some(&mut mappings),
2081        );
2082        if matches!(minify, MinifyType::Minify { .. }) {
2083            wr.set_indent_str("");
2084        }
2085
2086        let comments = comments.consumable();
2087
2088        let mut emitter = Emitter {
2089            cfg: swc_core::ecma::codegen::Config::default(),
2090            cm: source_map.clone(),
2091            comments: Some(&comments as &dyn Comments),
2092            wr,
2093        };
2094
2095        emitter.emit_program(&program)?;
2096        // Drop the AST eagerly so we don't keep it in memory while generating source maps
2097        drop(program);
2098    }
2099
2100    let source_map = if generate_source_map {
2101        let original_source_maps = original_source_map
2102            .iter()
2103            .map(|map| map.generate_source_map())
2104            .try_join()
2105            .await?;
2106        let original_source_maps = original_source_maps
2107            .iter()
2108            .filter_map(|map| map.as_content())
2109            .map(|map| map.content())
2110            .collect::<Vec<_>>();
2111
2112        Some(generate_js_source_map(
2113            &*source_map,
2114            mappings,
2115            original_source_maps,
2116            matches!(
2117                original_source_map,
2118                CodeGenResultOriginalSourceMap::Single(_)
2119            ),
2120            true,
2121            source_map_names,
2122        )?)
2123    } else {
2124        None
2125    };
2126
2127    Ok(EcmascriptModuleContent {
2128        inner_code: bytes.into(),
2129        source_map,
2130        is_esm,
2131        strict,
2132        additional_ids,
2133    }
2134    .cell())
2135}
2136
2137#[instrument(level = Level::TRACE, skip_all, name = "apply code generation")]
2138fn process_content_with_code_gens(
2139    program: &mut Program,
2140    globals: &Globals,
2141    code_gens: &mut Vec<CodeGeneration>,
2142) {
2143    let mut visitors = Vec::new();
2144    let mut root_visitors = Vec::new();
2145    let mut early_hoisted_stmts = FxIndexMap::default();
2146    let mut hoisted_stmts = FxIndexMap::default();
2147    let mut early_late_stmts = FxIndexMap::default();
2148    let mut late_stmts = FxIndexMap::default();
2149    for code_gen in code_gens {
2150        for CodeGenerationHoistedStmt { key, stmt } in code_gen.hoisted_stmts.drain(..) {
2151            hoisted_stmts.entry(key).or_insert(stmt);
2152        }
2153        for CodeGenerationHoistedStmt { key, stmt } in code_gen.early_hoisted_stmts.drain(..) {
2154            early_hoisted_stmts.insert(key.clone(), stmt);
2155        }
2156        for CodeGenerationHoistedStmt { key, stmt } in code_gen.late_stmts.drain(..) {
2157            late_stmts.insert(key.clone(), stmt);
2158        }
2159        for CodeGenerationHoistedStmt { key, stmt } in code_gen.early_late_stmts.drain(..) {
2160            early_late_stmts.insert(key.clone(), stmt);
2161        }
2162        for (path, visitor) in &code_gen.visitors {
2163            if path.is_empty() {
2164                root_visitors.push(&**visitor);
2165            } else {
2166                visitors.push((path, &**visitor));
2167            }
2168        }
2169    }
2170
2171    GLOBALS.set(globals, || {
2172        if !visitors.is_empty() {
2173            program.visit_mut_with_ast_path(
2174                &mut ApplyVisitors::new(visitors),
2175                &mut Default::default(),
2176            );
2177        }
2178        for pass in root_visitors {
2179            program.modify(pass);
2180        }
2181    });
2182
2183    match program {
2184        Program::Module(ast::Module { body, .. }) => {
2185            body.splice(
2186                0..0,
2187                early_hoisted_stmts
2188                    .into_values()
2189                    .chain(hoisted_stmts.into_values())
2190                    .map(ModuleItem::Stmt),
2191            );
2192            body.extend(
2193                early_late_stmts
2194                    .into_values()
2195                    .chain(late_stmts.into_values())
2196                    .map(ModuleItem::Stmt),
2197            );
2198        }
2199        Program::Script(Script { body, .. }) => {
2200            body.splice(
2201                0..0,
2202                early_hoisted_stmts
2203                    .into_values()
2204                    .chain(hoisted_stmts.into_values()),
2205            );
2206            body.extend(
2207                early_late_stmts
2208                    .into_values()
2209                    .chain(late_stmts.into_values()),
2210            );
2211        }
2212    };
2213}
2214
2215/// Like `hygiene`, but only renames the Atoms without clearing all SyntaxContexts
2216///
2217/// Don't rename idents marked with `is_import_mark` (i.e. a reference to a value which is imported
2218/// from another merged module) or listed in `preserve_exports` (i.e. an exported local binding):
2219/// even if they are causing collisions, they will be handled by the next hygiene pass over the
2220/// whole module.
2221fn hygiene_rename_only(
2222    top_level_mark: Option<Mark>,
2223    is_import_mark: Mark,
2224    preserved_exports: &FxHashSet<Id>,
2225) -> impl VisitMut {
2226    struct HygieneRenamer<'a> {
2227        preserved_exports: &'a FxHashSet<Id>,
2228        is_import_mark: Mark,
2229    }
2230    // Copied from `hygiene_with_config`'s HygieneRenamer, but added an `preserved_exports`
2231    impl swc_core::ecma::transforms::base::rename::Renamer for HygieneRenamer<'_> {
2232        type Target = Id;
2233
2234        const MANGLE: bool = false;
2235        const RESET_N: bool = true;
2236
2237        fn new_name_for(&self, orig: &Id, n: &mut usize) -> Atom {
2238            let res = if *n == 0 {
2239                orig.0.clone()
2240            } else {
2241                format!("{}{}", orig.0, n).into()
2242            };
2243            *n += 1;
2244            res
2245        }
2246
2247        fn preserve_name(&self, orig: &Id) -> bool {
2248            self.preserved_exports.contains(orig) || orig.1.has_mark(self.is_import_mark)
2249        }
2250    }
2251    swc_core::ecma::transforms::base::rename::renamer_keep_contexts(
2252        swc_core::ecma::transforms::base::hygiene::Config {
2253            top_level_mark: top_level_mark.unwrap_or_default(),
2254            ..Default::default()
2255        },
2256        HygieneRenamer {
2257            preserved_exports,
2258            is_import_mark,
2259        },
2260    )
2261}
2262
2263#[derive(Default)]
2264enum CodeGenResultSourceMap {
2265    #[default]
2266    /// No source map should be generated for this module
2267    None,
2268    Single {
2269        source_map: Arc<SourceMap>,
2270    },
2271    ScopeHoisting {
2272        /// The bitwidth of the modules header in the spans, see
2273        /// [CodeGenResultComments::encode_bytepos]
2274        modules_header_width: u32,
2275        lookup_table: Arc<Mutex<Vec<ModulePosition>>>,
2276        source_maps: Vec<CodeGenResultSourceMap>,
2277    },
2278}
2279
2280impl CodeGenResultSourceMap {
2281    fn is_some(&self) -> bool {
2282        match self {
2283            CodeGenResultSourceMap::None => false,
2284            CodeGenResultSourceMap::Single { .. }
2285            | CodeGenResultSourceMap::ScopeHoisting { .. } => true,
2286        }
2287    }
2288}
2289
2290impl Debug for CodeGenResultSourceMap {
2291    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2292        match self {
2293            CodeGenResultSourceMap::None => write!(f, "CodeGenResultSourceMap::None"),
2294            CodeGenResultSourceMap::Single { source_map } => {
2295                write!(
2296                    f,
2297                    "CodeGenResultSourceMap::Single {{ source_map: {:?} }}",
2298                    source_map.files().clone()
2299                )
2300            }
2301            CodeGenResultSourceMap::ScopeHoisting {
2302                modules_header_width,
2303                source_maps,
2304                ..
2305            } => write!(
2306                f,
2307                "CodeGenResultSourceMap::ScopeHoisting {{ modules_header_width: \
2308                 {modules_header_width}, source_maps: {source_maps:?} }}",
2309            ),
2310        }
2311    }
2312}
2313
2314impl Files for CodeGenResultSourceMap {
2315    fn try_lookup_source_file(
2316        &self,
2317        pos: BytePos,
2318    ) -> Result<Option<Arc<SourceFile>>, SourceMapLookupError> {
2319        match self {
2320            CodeGenResultSourceMap::None => Ok(None),
2321            CodeGenResultSourceMap::Single { source_map } => source_map.try_lookup_source_file(pos),
2322            CodeGenResultSourceMap::ScopeHoisting {
2323                modules_header_width,
2324                lookup_table,
2325                source_maps,
2326            } => {
2327                let (module, pos) =
2328                    CodeGenResultComments::decode_bytepos(*modules_header_width, pos, lookup_table);
2329                source_maps[module].try_lookup_source_file(pos)
2330            }
2331        }
2332    }
2333
2334    fn is_in_file(&self, f: &Arc<SourceFile>, raw_pos: BytePos) -> bool {
2335        match self {
2336            CodeGenResultSourceMap::None => false,
2337            CodeGenResultSourceMap::Single { .. } => f.start_pos <= raw_pos && raw_pos < f.end_pos,
2338            CodeGenResultSourceMap::ScopeHoisting { .. } => {
2339                // let (module, pos) = CodeGenResultComments::decode_bytepos(*modules_header_width,
2340                // pos);
2341
2342                // TODO optimize this, unfortunately, `SourceFile` doesn't know which `module` it
2343                // belongs from.
2344                false
2345            }
2346        }
2347    }
2348
2349    fn map_raw_pos(&self, pos: BytePos) -> BytePos {
2350        match self {
2351            CodeGenResultSourceMap::None => BytePos::DUMMY,
2352            CodeGenResultSourceMap::Single { .. } => pos,
2353            CodeGenResultSourceMap::ScopeHoisting {
2354                modules_header_width,
2355                lookup_table,
2356                ..
2357            } => CodeGenResultComments::decode_bytepos(*modules_header_width, pos, lookup_table).1,
2358        }
2359    }
2360}
2361
2362impl SourceMapper for CodeGenResultSourceMap {
2363    fn lookup_char_pos(&self, pos: BytePos) -> Loc {
2364        match self {
2365            CodeGenResultSourceMap::None => {
2366                panic!("CodeGenResultSourceMap::None cannot lookup_char_pos")
2367            }
2368            CodeGenResultSourceMap::Single { source_map } => source_map.lookup_char_pos(pos),
2369            CodeGenResultSourceMap::ScopeHoisting {
2370                modules_header_width,
2371                lookup_table,
2372                source_maps,
2373            } => {
2374                let (module, pos) =
2375                    CodeGenResultComments::decode_bytepos(*modules_header_width, pos, lookup_table);
2376                source_maps[module].lookup_char_pos(pos)
2377            }
2378        }
2379    }
2380    fn span_to_lines(&self, sp: Span) -> FileLinesResult {
2381        match self {
2382            CodeGenResultSourceMap::None => {
2383                panic!("CodeGenResultSourceMap::None cannot span_to_lines")
2384            }
2385            CodeGenResultSourceMap::Single { source_map } => source_map.span_to_lines(sp),
2386            CodeGenResultSourceMap::ScopeHoisting {
2387                modules_header_width,
2388                lookup_table,
2389                source_maps,
2390            } => {
2391                let (module, lo) = CodeGenResultComments::decode_bytepos(
2392                    *modules_header_width,
2393                    sp.lo,
2394                    lookup_table,
2395                );
2396                source_maps[module].span_to_lines(Span {
2397                    lo,
2398                    hi: CodeGenResultComments::decode_bytepos(
2399                        *modules_header_width,
2400                        sp.hi,
2401                        lookup_table,
2402                    )
2403                    .1,
2404                })
2405            }
2406        }
2407    }
2408    fn span_to_string(&self, sp: Span) -> String {
2409        match self {
2410            CodeGenResultSourceMap::None => {
2411                panic!("CodeGenResultSourceMap::None cannot span_to_string")
2412            }
2413            CodeGenResultSourceMap::Single { source_map } => source_map.span_to_string(sp),
2414            CodeGenResultSourceMap::ScopeHoisting {
2415                modules_header_width,
2416                lookup_table,
2417                source_maps,
2418            } => {
2419                let (module, lo) = CodeGenResultComments::decode_bytepos(
2420                    *modules_header_width,
2421                    sp.lo,
2422                    lookup_table,
2423                );
2424                source_maps[module].span_to_string(Span {
2425                    lo,
2426                    hi: CodeGenResultComments::decode_bytepos(
2427                        *modules_header_width,
2428                        sp.hi,
2429                        lookup_table,
2430                    )
2431                    .1,
2432                })
2433            }
2434        }
2435    }
2436    fn span_to_filename(&self, sp: Span) -> Arc<FileName> {
2437        match self {
2438            CodeGenResultSourceMap::None => {
2439                panic!("CodeGenResultSourceMap::None cannot span_to_filename")
2440            }
2441            CodeGenResultSourceMap::Single { source_map } => source_map.span_to_filename(sp),
2442            CodeGenResultSourceMap::ScopeHoisting {
2443                modules_header_width,
2444                lookup_table,
2445                source_maps,
2446            } => {
2447                let (module, lo) = CodeGenResultComments::decode_bytepos(
2448                    *modules_header_width,
2449                    sp.lo,
2450                    lookup_table,
2451                );
2452                source_maps[module].span_to_filename(Span {
2453                    lo,
2454                    hi: CodeGenResultComments::decode_bytepos(
2455                        *modules_header_width,
2456                        sp.hi,
2457                        lookup_table,
2458                    )
2459                    .1,
2460                })
2461            }
2462        }
2463    }
2464    fn merge_spans(&self, sp_lhs: Span, sp_rhs: Span) -> Option<Span> {
2465        match self {
2466            CodeGenResultSourceMap::None => {
2467                panic!("CodeGenResultSourceMap::None cannot merge_spans")
2468            }
2469            CodeGenResultSourceMap::Single { source_map } => source_map.merge_spans(sp_lhs, sp_rhs),
2470            CodeGenResultSourceMap::ScopeHoisting {
2471                modules_header_width,
2472                lookup_table,
2473                source_maps,
2474            } => {
2475                let (module_lhs, lo_lhs) = CodeGenResultComments::decode_bytepos(
2476                    *modules_header_width,
2477                    sp_lhs.lo,
2478                    lookup_table,
2479                );
2480                let (module_rhs, lo_rhs) = CodeGenResultComments::decode_bytepos(
2481                    *modules_header_width,
2482                    sp_rhs.lo,
2483                    lookup_table,
2484                );
2485                if module_lhs != module_rhs {
2486                    return None;
2487                }
2488                source_maps[module_lhs].merge_spans(
2489                    Span {
2490                        lo: lo_lhs,
2491                        hi: CodeGenResultComments::decode_bytepos(
2492                            *modules_header_width,
2493                            sp_lhs.hi,
2494                            lookup_table,
2495                        )
2496                        .1,
2497                    },
2498                    Span {
2499                        lo: lo_rhs,
2500                        hi: CodeGenResultComments::decode_bytepos(
2501                            *modules_header_width,
2502                            sp_rhs.hi,
2503                            lookup_table,
2504                        )
2505                        .1,
2506                    },
2507                )
2508            }
2509        }
2510    }
2511    fn call_span_if_macro(&self, sp: Span) -> Span {
2512        match self {
2513            CodeGenResultSourceMap::None => {
2514                panic!("CodeGenResultSourceMap::None cannot call_span_if_macro")
2515            }
2516            CodeGenResultSourceMap::Single { source_map } => source_map.call_span_if_macro(sp),
2517            CodeGenResultSourceMap::ScopeHoisting {
2518                modules_header_width,
2519                lookup_table,
2520                source_maps,
2521            } => {
2522                let (module, lo) = CodeGenResultComments::decode_bytepos(
2523                    *modules_header_width,
2524                    sp.lo,
2525                    lookup_table,
2526                );
2527                source_maps[module].call_span_if_macro(Span {
2528                    lo,
2529                    hi: CodeGenResultComments::decode_bytepos(
2530                        *modules_header_width,
2531                        sp.hi,
2532                        lookup_table,
2533                    )
2534                    .1,
2535                })
2536            }
2537        }
2538    }
2539    fn doctest_offset_line(&self, _line: usize) -> usize {
2540        panic!("doctest_offset_line is not implemented for CodeGenResultSourceMap");
2541    }
2542    fn span_to_snippet(&self, sp: Span) -> Result<String, Box<SpanSnippetError>> {
2543        match self {
2544            CodeGenResultSourceMap::None => Err(Box::new(SpanSnippetError::SourceNotAvailable {
2545                filename: FileName::Anon,
2546            })),
2547            CodeGenResultSourceMap::Single { source_map } => source_map.span_to_snippet(sp),
2548            CodeGenResultSourceMap::ScopeHoisting {
2549                modules_header_width,
2550                lookup_table,
2551                source_maps,
2552            } => {
2553                let (module, lo) = CodeGenResultComments::decode_bytepos(
2554                    *modules_header_width,
2555                    sp.lo,
2556                    lookup_table,
2557                );
2558                source_maps[module].span_to_snippet(Span {
2559                    lo,
2560                    hi: CodeGenResultComments::decode_bytepos(
2561                        *modules_header_width,
2562                        sp.hi,
2563                        lookup_table,
2564                    )
2565                    .1,
2566                })
2567            }
2568        }
2569    }
2570}
2571impl SourceMapperExt for CodeGenResultSourceMap {
2572    fn get_code_map(&self) -> &dyn SourceMapper {
2573        self
2574    }
2575}
2576
2577#[derive(Debug)]
2578enum CodeGenResultOriginalSourceMap {
2579    Single(Option<ResolvedVc<Box<dyn GenerateSourceMap>>>),
2580    ScopeHoisting(SmallVec<[ResolvedVc<Box<dyn GenerateSourceMap>>; 1]>),
2581}
2582
2583impl CodeGenResultOriginalSourceMap {
2584    fn iter(&self) -> impl Iterator<Item = ResolvedVc<Box<dyn GenerateSourceMap>>> {
2585        match self {
2586            CodeGenResultOriginalSourceMap::Single(map) => Either::Left(map.iter().copied()),
2587            CodeGenResultOriginalSourceMap::ScopeHoisting(maps) => {
2588                Either::Right(maps.iter().copied())
2589            }
2590        }
2591    }
2592}
2593
2594/// Stores a module index in position 0 and the full byte position of the source map in position 1
2595struct ModulePosition(u32, u32);
2596
2597enum CodeGenResultComments {
2598    Single {
2599        comments: Either<ImmutableComments, Arc<ImmutableComments>>,
2600        extra_comments: SwcComments,
2601    },
2602    ScopeHoisting {
2603        /// The bitwidth of the modules header in the spans, see
2604        /// [CodeGenResultComments::encode_bytepos]
2605        modules_header_width: u32,
2606        lookup_table: Arc<Mutex<Vec<ModulePosition>>>,
2607        comments: Vec<CodeGenResultComments>,
2608    },
2609    Empty,
2610}
2611
2612unsafe impl Send for CodeGenResultComments {}
2613unsafe impl Sync for CodeGenResultComments {}
2614
2615impl CodeGenResultComments {
2616    const CONTINUATION_BIT: u32 = 1 << 31;
2617    const SIGN_EXTENSION_BIT: u32 = 1 << 30;
2618
2619    #[inline]
2620    fn encode_bytepos_impl(
2621        modules_header_width: u32,
2622        module: u32,
2623        pos: BytePos,
2624        push_into_lookup: &mut impl FnMut(u32, u32) -> Result<u32>,
2625    ) -> Result<BytePos> {
2626        if pos.is_dummy() {
2627            // nothing to encode
2628            return Ok(pos);
2629        }
2630
2631        // Bit layout for encoded BytePos (32 bits):
2632        // [31] Continuation bit. If set (1), the remaining 31 bits [0..30] encode an index into
2633        //      the lookup vector where (module, original_bytepos) is stored.
2634        //      In this case, decoding ignores other fields and fetches from the table.
2635        // If not set (0):
2636        // [30] Sign-extend bit. Indicates whether the stolen high bits of the original bytepos
2637        //      were all 1s (1) or all 0s (0), so that decoding can restore the original high bits.
2638        // [30 - modules_header_width + 1 .. 30) Module id: modules_header_width bits immediately
2639        //      below the sign-extend bit.
2640        // [0 .. (32 - (2 + modules_header_width)) ) Remaining low bits store the truncated bytepos.
2641        //
2642        // Notes:
2643        // - We reserve 2 header bits always (continuation + sign-extend), so header_width =
2644        //   modules_header_width + 2, and pos_width = 32 - header_width.
2645        // - When the original value does not fit in the available pos_width with a uniform high bit
2646        //   pattern, we spill (set continuation) and store (module, pos) in the lookup table and
2647        //   encode the index with the continuation bit set.
2648        //
2649        // Example (diagrammatic only):
2650        // modules_header_width = 4
2651        // Key:
2652        // (c = continuation, s = sign-extend, m = module, p = pos bits, i = lookup table index)
2653        //
2654        // The continuation bit is set, and the remaining 31 bits are reinterpreted as the index
2655        // into the lookup table.
2656        // Bytes: 1iii iiii iiii iiii iiii iiii iiii iiii
2657        //
2658        // The continuation bit is not set,
2659        // Bytes: 0smm mmpp pppp pppp pppp pppp pppp pppp
2660
2661        let header_width = modules_header_width + 2;
2662        let pos_width = 32 - header_width;
2663
2664        let pos = pos.0;
2665
2666        let old_high_bits = pos >> pos_width;
2667        let high_bits_set = if (2u32.pow(header_width) - 1) == old_high_bits {
2668            true
2669        } else if old_high_bits == 0 {
2670            false
2671        } else {
2672            // The integer is too large for our desired header width and we need to store the result
2673            // in our vector and set the flag to reinterpret this data as the index of
2674            // the vector where the element is being stored.
2675            let ix = push_into_lookup(module, pos)?;
2676            // Make sure that the index fits within the allotted bits
2677            assert_eq!(ix & CodeGenResultComments::CONTINUATION_BIT, 0);
2678
2679            return Ok(BytePos(ix | CodeGenResultComments::CONTINUATION_BIT));
2680        };
2681
2682        let pos = pos & !((2u32.pow(header_width) - 1) << pos_width);
2683        let encoded_high_bits = if high_bits_set {
2684            CodeGenResultComments::SIGN_EXTENSION_BIT
2685        } else {
2686            0
2687        };
2688        let encoded_module = module << pos_width;
2689
2690        Ok(BytePos(encoded_module | encoded_high_bits | pos))
2691    }
2692
2693    fn take(&mut self) -> Self {
2694        std::mem::replace(self, CodeGenResultComments::Empty)
2695    }
2696
2697    fn consumable(&self) -> CodeGenResultCommentsConsumable<'_> {
2698        match self {
2699            CodeGenResultComments::Single {
2700                comments,
2701                extra_comments,
2702            } => CodeGenResultCommentsConsumable::Single {
2703                comments: match comments {
2704                    Either::Left(comments) => comments.consumable(),
2705                    Either::Right(comments) => comments.consumable(),
2706                },
2707                extra_comments,
2708            },
2709            CodeGenResultComments::ScopeHoisting {
2710                modules_header_width,
2711                lookup_table,
2712                comments,
2713            } => CodeGenResultCommentsConsumable::ScopeHoisting {
2714                modules_header_width: *modules_header_width,
2715                lookup_table: lookup_table.clone(),
2716                comments: comments.iter().map(|c| c.consumable()).collect(),
2717            },
2718            CodeGenResultComments::Empty => CodeGenResultCommentsConsumable::Empty,
2719        }
2720    }
2721
2722    fn encode_bytepos(
2723        modules_header_width: u32,
2724        module: u32,
2725        pos: BytePos,
2726        lookup_table: Arc<Mutex<Vec<ModulePosition>>>,
2727    ) -> Result<BytePos> {
2728        let mut push = |module: u32, pos_u32: u32| -> Result<u32> {
2729            let mut lookup_table = lookup_table
2730                .lock()
2731                .map_err(|_| anyhow!("Failed to grab lock on the index map for byte positions"))?;
2732            let ix = lookup_table.len() as u32;
2733            if ix >= 1 << 30 {
2734                bail!("Too many byte positions being stored");
2735            }
2736            lookup_table.push(ModulePosition(module, pos_u32));
2737            Ok(ix)
2738        };
2739        Self::encode_bytepos_impl(modules_header_width, module, pos, &mut push)
2740    }
2741
2742    fn encode_bytepos_with_vec(
2743        modules_header_width: u32,
2744        module: u32,
2745        pos: BytePos,
2746        lookup_table: &mut Vec<ModulePosition>,
2747    ) -> Result<BytePos> {
2748        let mut push = |module: u32, pos_u32: u32| -> Result<u32> {
2749            let ix = lookup_table.len() as u32;
2750            if ix >= 1 << 30 {
2751                bail!("Too many byte positions being stored");
2752            }
2753            lookup_table.push(ModulePosition(module, pos_u32));
2754            Ok(ix)
2755        };
2756        Self::encode_bytepos_impl(modules_header_width, module, pos, &mut push)
2757    }
2758
2759    fn decode_bytepos(
2760        modules_header_width: u32,
2761        pos: BytePos,
2762        lookup_table: &Mutex<Vec<ModulePosition>>,
2763    ) -> (usize, BytePos) {
2764        if pos.is_dummy() {
2765            // nothing to decode
2766            panic!("Cannot decode dummy BytePos");
2767        }
2768
2769        let header_width = modules_header_width + 2;
2770        let pos_width = 32 - header_width;
2771
2772        if (CodeGenResultComments::CONTINUATION_BIT & pos.0)
2773            == CodeGenResultComments::CONTINUATION_BIT
2774        {
2775            let lookup_table = lookup_table
2776                .lock()
2777                .expect("Failed to grab lock on the index map for byte position");
2778            let ix = pos.0 & !CodeGenResultComments::CONTINUATION_BIT;
2779            let ModulePosition(module, pos) = lookup_table[ix as usize];
2780
2781            return (module as usize, BytePos(pos));
2782        }
2783
2784        let high_bits_set = pos.0 >> 30 & 1 == 1;
2785        let module = (pos.0 << 2) >> (pos_width + 2);
2786        let pos = pos.0 & !((2u32.pow(header_width) - 1) << pos_width);
2787        let pos = if high_bits_set {
2788            pos | ((2u32.pow(header_width) - 1) << pos_width)
2789        } else {
2790            pos
2791        };
2792        (module as usize, BytePos(pos))
2793    }
2794}
2795
2796enum CodeGenResultCommentsConsumable<'a> {
2797    Single {
2798        comments: CowComments<'a>,
2799        extra_comments: &'a SwcComments,
2800    },
2801    ScopeHoisting {
2802        modules_header_width: u32,
2803        lookup_table: Arc<Mutex<Vec<ModulePosition>>>,
2804        comments: Vec<CodeGenResultCommentsConsumable<'a>>,
2805    },
2806    Empty,
2807}
2808/// All BytePos in Spans in the AST are encoded correctly in [`merge_modules`], but the Comments
2809/// also contain spans. These also need to be encoded so that all pos in `mappings` are consistently
2810/// encoded.
2811fn encode_module_into_comment_span(
2812    modules_header_width: u32,
2813    module: usize,
2814    mut comment: Comment,
2815    lookup_table: Arc<Mutex<Vec<ModulePosition>>>,
2816) -> Comment {
2817    comment.span.lo = CodeGenResultComments::encode_bytepos(
2818        modules_header_width,
2819        module as u32,
2820        comment.span.lo,
2821        lookup_table.clone(),
2822    )
2823    .unwrap();
2824    comment.span.hi = CodeGenResultComments::encode_bytepos(
2825        modules_header_width,
2826        module as u32,
2827        comment.span.hi,
2828        lookup_table,
2829    )
2830    .unwrap();
2831    comment
2832}
2833
2834impl Comments for CodeGenResultCommentsConsumable<'_> {
2835    fn add_leading(&self, _pos: BytePos, _cmt: Comment) {
2836        unimplemented!("add_leading")
2837    }
2838
2839    fn add_leading_comments(&self, _pos: BytePos, _comments: Vec<Comment>) {
2840        unimplemented!("add_leading_comments")
2841    }
2842
2843    fn has_leading(&self, pos: BytePos) -> bool {
2844        if pos.is_dummy() {
2845            return false;
2846        }
2847        match self {
2848            Self::Single {
2849                comments,
2850                extra_comments,
2851            } => comments.has_leading(pos) || extra_comments.has_leading(pos),
2852            Self::ScopeHoisting {
2853                modules_header_width,
2854                lookup_table,
2855                comments,
2856            } => {
2857                let (module, pos) =
2858                    CodeGenResultComments::decode_bytepos(*modules_header_width, pos, lookup_table);
2859                comments[module].has_leading(pos)
2860            }
2861            Self::Empty => false,
2862        }
2863    }
2864
2865    fn move_leading(&self, _from: BytePos, _to: BytePos) {
2866        unimplemented!("move_leading")
2867    }
2868
2869    fn take_leading(&self, pos: BytePos) -> Option<Vec<Comment>> {
2870        if pos.is_dummy() {
2871            return None;
2872        }
2873        match self {
2874            Self::Single {
2875                comments,
2876                extra_comments,
2877            } => merge_option_vec(comments.take_leading(pos), extra_comments.take_leading(pos)),
2878            Self::ScopeHoisting {
2879                modules_header_width,
2880                lookup_table,
2881                comments,
2882            } => {
2883                let (module, pos) =
2884                    CodeGenResultComments::decode_bytepos(*modules_header_width, pos, lookup_table);
2885                comments[module].take_leading(pos).map(|comments| {
2886                    comments
2887                        .into_iter()
2888                        .map(|c| {
2889                            encode_module_into_comment_span(
2890                                *modules_header_width,
2891                                module,
2892                                c,
2893                                lookup_table.clone(),
2894                            )
2895                        })
2896                        .collect()
2897                })
2898            }
2899            Self::Empty => None,
2900        }
2901    }
2902
2903    fn get_leading(&self, pos: BytePos) -> Option<Vec<Comment>> {
2904        if pos.is_dummy() {
2905            return None;
2906        }
2907        match self {
2908            Self::Single {
2909                comments,
2910                extra_comments,
2911            } => merge_option_vec(comments.get_leading(pos), extra_comments.get_leading(pos)),
2912            Self::ScopeHoisting {
2913                modules_header_width,
2914                lookup_table,
2915                comments,
2916            } => {
2917                let (module, pos) =
2918                    CodeGenResultComments::decode_bytepos(*modules_header_width, pos, lookup_table);
2919                comments[module].get_leading(pos).map(|comments| {
2920                    comments
2921                        .into_iter()
2922                        .map(|c| {
2923                            encode_module_into_comment_span(
2924                                *modules_header_width,
2925                                module,
2926                                c,
2927                                lookup_table.clone(),
2928                            )
2929                        })
2930                        .collect()
2931                })
2932            }
2933            Self::Empty => None,
2934        }
2935    }
2936
2937    fn add_trailing(&self, _pos: BytePos, _cmt: Comment) {
2938        unimplemented!("add_trailing")
2939    }
2940
2941    fn add_trailing_comments(&self, _pos: BytePos, _comments: Vec<Comment>) {
2942        unimplemented!("add_trailing_comments")
2943    }
2944
2945    fn has_trailing(&self, pos: BytePos) -> bool {
2946        if pos.is_dummy() {
2947            return false;
2948        }
2949        match self {
2950            Self::Single {
2951                comments,
2952                extra_comments,
2953            } => comments.has_trailing(pos) || extra_comments.has_trailing(pos),
2954            Self::ScopeHoisting {
2955                modules_header_width,
2956                lookup_table,
2957                comments,
2958            } => {
2959                let (module, pos) =
2960                    CodeGenResultComments::decode_bytepos(*modules_header_width, pos, lookup_table);
2961                comments[module].has_trailing(pos)
2962            }
2963            Self::Empty => false,
2964        }
2965    }
2966
2967    fn move_trailing(&self, _from: BytePos, _to: BytePos) {
2968        unimplemented!("move_trailing")
2969    }
2970
2971    fn take_trailing(&self, pos: BytePos) -> Option<Vec<Comment>> {
2972        if pos.is_dummy() {
2973            return None;
2974        }
2975        match self {
2976            Self::Single {
2977                comments,
2978                extra_comments,
2979            } => merge_option_vec(
2980                comments.take_trailing(pos),
2981                extra_comments.take_trailing(pos),
2982            ),
2983            Self::ScopeHoisting {
2984                modules_header_width,
2985                lookup_table,
2986                comments,
2987            } => {
2988                let (module, pos) =
2989                    CodeGenResultComments::decode_bytepos(*modules_header_width, pos, lookup_table);
2990                comments[module].take_trailing(pos).map(|comments| {
2991                    comments
2992                        .into_iter()
2993                        .map(|c| {
2994                            encode_module_into_comment_span(
2995                                *modules_header_width,
2996                                module,
2997                                c,
2998                                lookup_table.clone(),
2999                            )
3000                        })
3001                        .collect()
3002                })
3003            }
3004            Self::Empty => None,
3005        }
3006    }
3007
3008    fn get_trailing(&self, pos: BytePos) -> Option<Vec<Comment>> {
3009        if pos.is_dummy() {
3010            return None;
3011        }
3012        match self {
3013            Self::Single {
3014                comments,
3015                extra_comments,
3016            } => merge_option_vec(comments.get_leading(pos), extra_comments.get_leading(pos)),
3017            Self::ScopeHoisting {
3018                modules_header_width,
3019                lookup_table,
3020                comments,
3021            } => {
3022                let (module, pos) =
3023                    CodeGenResultComments::decode_bytepos(*modules_header_width, pos, lookup_table);
3024                comments[module].get_leading(pos).map(|comments| {
3025                    comments
3026                        .into_iter()
3027                        .map(|c| {
3028                            encode_module_into_comment_span(
3029                                *modules_header_width,
3030                                module,
3031                                c,
3032                                lookup_table.clone(),
3033                            )
3034                        })
3035                        .collect()
3036                })
3037            }
3038            Self::Empty => None,
3039        }
3040    }
3041
3042    fn add_pure_comment(&self, _pos: BytePos) {
3043        unimplemented!("add_pure_comment")
3044    }
3045}
3046
3047fn merge_option_vec<T>(a: Option<Vec<T>>, b: Option<Vec<T>>) -> Option<Vec<T>> {
3048    match (a, b) {
3049        (Some(a), Some(b)) => Some(a.into_iter().chain(b).collect()),
3050        (Some(a), None) => Some(a),
3051        (None, Some(b)) => Some(b),
3052        (None, None) => None,
3053    }
3054}
3055
3056#[cfg(test)]
3057mod tests {
3058    use super::*;
3059    fn bytepos_ensure_identical(modules_header_width: u32, pos: BytePos) {
3060        let module_count = 2u32.pow(modules_header_width);
3061        let lookup_table = Arc::new(Mutex::new(Vec::new()));
3062
3063        for module in [
3064            0,
3065            1,
3066            2,
3067            module_count / 2,
3068            module_count.wrapping_sub(5),
3069            module_count.wrapping_sub(1),
3070        ]
3071        .into_iter()
3072        .filter(|&m| m < module_count)
3073        {
3074            let encoded = CodeGenResultComments::encode_bytepos(
3075                modules_header_width,
3076                module,
3077                pos,
3078                lookup_table.clone(),
3079            )
3080            .unwrap();
3081            let (decoded_module, decoded_pos) =
3082                CodeGenResultComments::decode_bytepos(modules_header_width, encoded, &lookup_table);
3083            assert_eq!(
3084                decoded_module as u32, module,
3085                "Testing width {modules_header_width} and pos {pos:?}"
3086            );
3087            assert_eq!(
3088                decoded_pos, pos,
3089                "Testing width {modules_header_width} and pos {pos:?}"
3090            );
3091        }
3092    }
3093
3094    #[test]
3095    fn test_encode_decode_bytepos_format() {
3096        let table = Arc::new(Mutex::new(Vec::new()));
3097
3098        for (pos, module, modules_header_width, result) in [
3099            (
3100                0b00000000000000000000000000000101,
3101                0b1,
3102                1,
3103                0b00100000000000000000000000000101,
3104            ),
3105            (
3106                0b00000000000000000000000000000101,
3107                0b01,
3108                2,
3109                0b00010000000000000000000000000101,
3110            ),
3111            (
3112                0b11111111111111110000000000000101,
3113                0b0110,
3114                4,
3115                0b01011011111111110000000000000101,
3116            ),
3117            (
3118                BytePos::PLACEHOLDER.0,
3119                0b01111,
3120                5,
3121                0b01011111111111111111111111111101,
3122            ),
3123            (
3124                BytePos::PURE.0,
3125                0b01111,
3126                5,
3127                0b01011111111111111111111111111110,
3128            ),
3129            (
3130                BytePos::SYNTHESIZED.0,
3131                0b01111,
3132                5,
3133                0b01011111111111111111111111111111,
3134            ),
3135            // This is an index that should trigger the overflow to store the position into the
3136            // lookup table
3137            (
3138                0b00000111111111110000000000000101,
3139                0b0001,
3140                4,
3141                0b10000000000000000000000000000000,
3142            ),
3143            // Another one should increase the index by 1
3144            (
3145                0b00000111111111110000000000111110,
3146                0b0001,
3147                4,
3148                0b10000000000000000000000000000001,
3149            ),
3150            // Special case, DUMMY stays a DUMMY
3151            (BytePos::DUMMY.0, 0b0001, 4, BytePos::DUMMY.0),
3152        ] {
3153            let encoded = CodeGenResultComments::encode_bytepos(
3154                modules_header_width,
3155                module,
3156                BytePos(pos),
3157                table.clone(),
3158            )
3159            .unwrap();
3160            assert_eq!(encoded.0, result);
3161
3162            // Ensure that the correct original module and bytepos are stored when overflow occurs
3163            if encoded.0 & CodeGenResultComments::CONTINUATION_BIT
3164                == CodeGenResultComments::CONTINUATION_BIT
3165            {
3166                let index = encoded.0 & !CodeGenResultComments::CONTINUATION_BIT;
3167                let ModulePosition(encoded_module, encoded_pos) =
3168                    table.lock().unwrap()[index as usize];
3169                assert_eq!(encoded_module, module);
3170                assert_eq!(encoded_pos, pos);
3171            }
3172        }
3173    }
3174
3175    #[test]
3176    fn test_encode_decode_bytepos_lossless() {
3177        // This is copied from swc (it's not exported), comments the range above this value.
3178        const DUMMY_RESERVE: u32 = u32::MAX - 2_u32.pow(16);
3179
3180        for modules_header_width in 1..=10 {
3181            for pos in [
3182                // BytePos::DUMMY, // This must never get decoded in the first place
3183                BytePos(1),
3184                BytePos(2),
3185                BytePos(100),
3186                BytePos(4_000_000),
3187                BytePos(600_000_000),
3188                BytePos(u32::MAX - 3), // The maximum allowed value that isn't reserved by SWC
3189                BytePos::PLACEHOLDER,
3190                BytePos::SYNTHESIZED,
3191                BytePos::PURE,
3192                BytePos(DUMMY_RESERVE),
3193                BytePos(DUMMY_RESERVE + 10),
3194                BytePos(DUMMY_RESERVE + 10000),
3195            ] {
3196                bytepos_ensure_identical(modules_header_width, pos);
3197            }
3198        }
3199    }
3200}