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