Skip to main content

turbopack_ecmascript/
lib.rs

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