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