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