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