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