Skip to main content

turbopack_ecmascript/
lib.rs

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