Skip to main content

turbopack_ecmascript/
lib.rs

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