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