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