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