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(true));
1006        }
1007
1008        Ok(Vc::cell(false))
1009    }
1010
1011    #[turbo_tasks::function]
1012    async fn merge(
1013        self: Vc<Self>,
1014        modules: Vc<MergeableModulesExposed>,
1015        entry_points: Vc<MergeableModules>,
1016    ) -> Result<Vc<Box<dyn ChunkableModule>>> {
1017        Ok(Vc::upcast(
1018            *MergedEcmascriptModule::new(
1019                modules,
1020                entry_points,
1021                self.options().to_resolved().await?,
1022            )
1023            .await?,
1024        ))
1025    }
1026}
1027
1028#[turbo_tasks::value_impl]
1029impl EvaluatableAsset for EcmascriptModuleAsset {}
1030
1031#[turbo_tasks::value_impl]
1032impl ResolveOrigin for EcmascriptModuleAsset {
1033    fn origin_path(&self) -> FileSystemPath {
1034        self.origin_path.clone()
1035    }
1036
1037    fn asset_context(&self) -> ResolvedVc<Box<dyn AssetContext>> {
1038        self.asset_context
1039    }
1040}
1041
1042/// The transformed contents of an Ecmascript module.
1043#[turbo_tasks::value(shared)]
1044pub struct EcmascriptModuleContent {
1045    pub inner_code: Rope,
1046    pub source_map: Option<StructuredSourceMap>,
1047    pub is_esm: bool,
1048    pub strict: bool,
1049    pub additional_ids: SmallVec<[ModuleId; 1]>,
1050}
1051
1052#[turbo_tasks::value(shared)]
1053#[derive(Clone, Debug, Hash)]
1054pub struct EcmascriptModuleContentOptions {
1055    module: ResolvedVc<Box<dyn EcmascriptChunkPlaceable>>,
1056    parsed: Option<ResolvedVc<ParseResult>>,
1057    specified_module_type: SpecifiedModuleType,
1058    chunking_context: ResolvedVc<Box<dyn ChunkingContext>>,
1059    references: ResolvedVc<ModuleReferences>,
1060    part_references: Vec<ResolvedVc<EcmascriptModulePartReference>>,
1061    esm_references: ResolvedVc<EsmAssetReferences>,
1062    code_generation: ResolvedVc<CodeGens>,
1063    async_module: ResolvedVc<OptionAsyncModule>,
1064    generate_source_map: bool,
1065    original_source_map: Option<ResolvedVc<Box<dyn GenerateSourceMap>>>,
1066    exports: ResolvedVc<EcmascriptExports>,
1067    /// Set by synthetic modules that re-export but have no source of their own to analyze, so
1068    /// there is no `ImportMap` to classify them from.
1069    export_registration_mode: Option<ExportRegistrationMode>,
1070    async_module_info: Option<ResolvedVc<AsyncModuleInfo>>,
1071}
1072
1073/// Generate the code of a processed virtual module under the identity of the module in the
1074/// graph. The code source supplies its parsed program, references, exports and async behavior;
1075/// the graph module supplies the identity for export usage and other code generation.
1076#[turbo_tasks::function]
1077pub async fn chunk_item_content_with_code_from(
1078    module: ResolvedVc<Box<dyn EcmascriptChunkPlaceable>>,
1079    code_source: ResolvedVc<Box<dyn EcmascriptChunkPlaceable>>,
1080    chunking_context: ResolvedVc<Box<dyn ChunkingContext>>,
1081    async_module_info: Option<Vc<AsyncModuleInfo>>,
1082) -> Result<Vc<EcmascriptChunkItemContent>> {
1083    let Some(analyzable) = ResolvedVc::try_sidecast::<Box<dyn EcmascriptAnalyzable>>(code_source)
1084    else {
1085        bail!("virtual ECMAScript code source must be analyzable");
1086    };
1087    let source_options = analyzable
1088        .module_content_options(*chunking_context, async_module_info)
1089        .await?;
1090    let mut options = (*source_options).clone();
1091    options.module = module;
1092    let content = EcmascriptModuleContent::new(options.cell());
1093    let async_module_options = code_source
1094        .get_async_module()
1095        .module_options(async_module_info);
1096    Ok(EcmascriptChunkItemContent::new(
1097        content,
1098        *chunking_context,
1099        async_module_options,
1100    ))
1101}
1102
1103impl EcmascriptModuleContentOptions {
1104    async fn merged_code_gens(
1105        &self,
1106        scope_hoisting_context: ScopeHoistingContext<'_>,
1107        eval_context: &EvalContext,
1108    ) -> Result<(Vec<CodeGeneration>, ReadRef<CodeGens>)> {
1109        // Don't read `parsed` here again, it will cause a recomputation as `process_parse_result`
1110        // has consumed the cell already.
1111        let EcmascriptModuleContentOptions {
1112            module,
1113            chunking_context,
1114            references,
1115            part_references,
1116            esm_references,
1117            code_generation,
1118            async_module,
1119            exports,
1120            export_registration_mode,
1121            async_module_info,
1122            ..
1123        } = self;
1124
1125        async {
1126            let async_module_ref = async_module.await?;
1127            let esm_references_ref = esm_references.await?;
1128            // The export registration is computed first: when it can spell the whole module's
1129            // exports as one compact call, it also performs those imports, and reports which
1130            // references it subsumed so they don't emit them a second time.
1131            let (exports_code_gen, subsumed_imports) =
1132                if let EcmascriptExports::EsmExports(exports) = *exports.await? {
1133                    let (code_gen, subsumed) = exports
1134                        .code_generation(
1135                            **chunking_context,
1136                            scope_hoisting_context,
1137                            eval_context,
1138                            *module,
1139                            export_registration_mode
1140                                .unwrap_or_else(|| eval_context.imports.export_registration_mode()),
1141                            export_registration_mode
1142                                .map(|_| (&part_references[..], &esm_references_ref[..])),
1143                            async_module_info.is_some(),
1144                        )
1145                        .await?;
1146                    (Some(code_gen), subsumed)
1147                } else {
1148                    (None, Default::default())
1149                };
1150
1151            let additional_code_gens = [
1152                if let Some(async_module) = &*async_module_ref {
1153                    Some(
1154                        async_module
1155                            .code_generation(
1156                                async_module_info.map(|info| *info),
1157                                **references,
1158                                **chunking_context,
1159                            )
1160                            .await?,
1161                    )
1162                } else {
1163                    None
1164                },
1165                exports_code_gen,
1166            ];
1167
1168            let part_code_gens = part_references
1169                .iter()
1170                .map(|r| {
1171                    r.code_generation(
1172                        **chunking_context,
1173                        scope_hoisting_context,
1174                        &subsumed_imports.namespaces,
1175                    )
1176                })
1177                .try_join()
1178                .await?;
1179
1180            let esm_code_gens = esm_references_ref
1181                .iter()
1182                .map(|r| {
1183                    r.code_generation(
1184                        **chunking_context,
1185                        scope_hoisting_context,
1186                        &subsumed_imports,
1187                    )
1188                })
1189                .try_join()
1190                .await?;
1191
1192            let code_generation = code_generation.await?;
1193            let code_gens = code_generation
1194                .code_gens
1195                .iter()
1196                .map(|c| {
1197                    c.code_generation(
1198                        &code_generation.ast_paths,
1199                        **chunking_context,
1200                        scope_hoisting_context,
1201                        *module,
1202                        *exports,
1203                    )
1204                })
1205                .try_join()
1206                .await?;
1207
1208            anyhow::Ok((
1209                part_code_gens
1210                    .into_iter()
1211                    .chain(esm_code_gens)
1212                    .chain(additional_code_gens.into_iter().flatten())
1213                    .chain(code_gens)
1214                    .collect(),
1215                code_generation,
1216            ))
1217        }
1218        .instrument(tracing::info_span!("precompute code generation"))
1219        .await
1220    }
1221}
1222
1223#[turbo_tasks::value_impl]
1224impl EcmascriptModuleContent {
1225    /// Creates a new [`Vc<EcmascriptModuleContent>`].
1226    #[turbo_tasks::function]
1227    pub async fn new(input: Vc<EcmascriptModuleContentOptions>) -> Result<Vc<Self>> {
1228        let input = input.await?;
1229        let EcmascriptModuleContentOptions {
1230            parsed,
1231            module,
1232            specified_module_type,
1233            generate_source_map,
1234            original_source_map,
1235            chunking_context,
1236            ..
1237        } = &*input;
1238
1239        let minify = chunking_context.minify_type().await?;
1240
1241        let content = process_parse_result(
1242            *parsed,
1243            module.ident(),
1244            *specified_module_type,
1245            *generate_source_map,
1246            *original_source_map,
1247            *minify,
1248            Some(&*input),
1249            None,
1250        )
1251        .await?;
1252        emit_content(content, Default::default()).await
1253    }
1254
1255    /// Creates a new [`Vc<EcmascriptModuleContent>`] without an analysis pass.
1256    #[turbo_tasks::function]
1257    pub async fn new_without_analysis(
1258        parsed: Vc<ParseResult>,
1259        ident: Vc<AssetIdent>,
1260        specified_module_type: SpecifiedModuleType,
1261        generate_source_map: bool,
1262    ) -> Result<Vc<Self>> {
1263        let content = process_parse_result(
1264            Some(parsed.to_resolved().await?),
1265            ident,
1266            specified_module_type,
1267            generate_source_map,
1268            None,
1269            MinifyType::NoMinify,
1270            None,
1271            None,
1272        )
1273        .await?;
1274        emit_content(content, Default::default()).await
1275    }
1276
1277    /// Creates a new [`Vc<EcmascriptModuleContent>`] from multiple modules, performing scope
1278    /// hoisting.
1279    /// - The `modules` argument is a list of all modules to be merged (and whether their exports
1280    ///   should be exposed).
1281    /// - The `entries` argument is a list of modules that should be treated as entry points for the
1282    ///   merged module (used to determine execution order).
1283    #[turbo_tasks::function]
1284    pub async fn new_merged(
1285        modules: Vec<(
1286            ResolvedVc<Box<dyn EcmascriptAnalyzable>>,
1287            MergeableModuleExposure,
1288        )>,
1289        module_options: Vec<Vc<EcmascriptModuleContentOptions>>,
1290        entry_points: Vec<ResolvedVc<Box<dyn EcmascriptAnalyzable>>>,
1291    ) -> Result<Vc<Self>> {
1292        async {
1293            let modules = modules
1294                .into_iter()
1295                .map(|(m, exposed)| {
1296                    (
1297                        ResolvedVc::try_sidecast::<Box<dyn EcmascriptChunkPlaceable>>(m).unwrap(),
1298                        exposed,
1299                    )
1300                })
1301                .collect::<FxIndexMap<_, _>>();
1302            let entry_points = entry_points
1303                .into_iter()
1304                .map(|m| {
1305                    let m =
1306                        ResolvedVc::try_sidecast::<Box<dyn EcmascriptChunkPlaceable>>(m).unwrap();
1307                    (m, modules.get_index_of(&m).unwrap())
1308                })
1309                .collect::<Vec<_>>();
1310
1311            let globals_merged = Globals::default();
1312
1313            let contents = module_options
1314                .iter()
1315                .map(async |options| {
1316                    let options = options.await?;
1317                    let EcmascriptModuleContentOptions {
1318                        chunking_context,
1319                        parsed,
1320                        module,
1321                        specified_module_type,
1322                        generate_source_map,
1323                        original_source_map,
1324                        ..
1325                    } = &*options;
1326
1327                    let result = process_parse_result(
1328                        *parsed,
1329                        module.ident(),
1330                        *specified_module_type,
1331                        *generate_source_map,
1332                        *original_source_map,
1333                        *chunking_context.minify_type().await?,
1334                        Some(&*options),
1335                        Some(ScopeHoistingOptions {
1336                            module: *module,
1337                            modules: &modules,
1338                        }),
1339                    )
1340                    .await?;
1341
1342                    Ok((*module, result))
1343                })
1344                .try_join()
1345                .await?;
1346
1347            let (merged_ast, comments, source_maps, original_source_maps, lookup_table) =
1348                merge_modules(contents, &entry_points, &globals_merged).await?;
1349
1350            // Use the options from an arbitrary module, since they should all be the same with
1351            // regards to minify_type and chunking_context.
1352            let options = module_options.last().unwrap().await?;
1353
1354            let modules_header_width = modules.len().next_power_of_two().trailing_zeros();
1355            let content = CodeGenResult {
1356                program: merged_ast,
1357                source_map: CodeGenResultSourceMap::ScopeHoisting {
1358                    modules_header_width,
1359                    lookup_table: lookup_table.clone(),
1360                    source_maps,
1361                },
1362                comments: CodeGenResultComments::ScopeHoisting {
1363                    modules_header_width,
1364                    lookup_table,
1365                    comments,
1366                },
1367                is_esm: true,
1368                strict: true,
1369                original_source_map: CodeGenResultOriginalSourceMap::ScopeHoisting(
1370                    original_source_maps,
1371                ),
1372                minify: *options.chunking_context.minify_type().await?,
1373                scope_hoisting_syntax_contexts: None,
1374            };
1375
1376            let first_entry = entry_points.first().unwrap().0;
1377            let additional_ids = modules
1378                .keys()
1379                // Additionally set this module factory for all modules that are exposed. The whole
1380                // group might be imported via a different entry import in different chunks (we only
1381                // ensure that the modules are in the same order, not that they form a subgraph that
1382                // is always imported from the same root module).
1383                //
1384                // Also skip the first entry, which is the name of the chunk item.
1385                .filter(|m| {
1386                    **m != first_entry
1387                        && *modules.get(*m).unwrap() == MergeableModuleExposure::External
1388                })
1389                .map(|m| m.chunk_item_id(*options.chunking_context))
1390                .try_join()
1391                .await?
1392                .into();
1393
1394            emit_content(content, additional_ids)
1395                .instrument(tracing::info_span!("emit code"))
1396                .await
1397        }
1398        .instrument(tracing::info_span!(
1399            "generate merged code",
1400            modules = module_options.len()
1401        ))
1402        .await
1403    }
1404}
1405
1406/// Comments delimiting the early hoisted statements, which [`merge_modules`] moves in front of the
1407/// merged module so that a cyclic importer can't re-enter it before they ran.
1408const EARLY_HOIST_START: &str = " TURBOPACK EARLY HOIST START";
1409const EARLY_HOIST_END: &str = " TURBOPACK EARLY HOIST END";
1410
1411fn early_hoist_comment(text: &str) -> Comment {
1412    Comment {
1413        kind: CommentKind::Line,
1414        span: DUMMY_SP,
1415        text: text.into(),
1416    }
1417}
1418
1419/// Finds the statements delimited by [`EARLY_HOIST_START`] and [`EARLY_HOIST_END`], as an inclusive
1420/// index range over `body` covering both delimiters. Must run before the spans are rewritten.
1421fn early_hoist_range(
1422    comments: &SwcComments,
1423    body: impl Iterator<Item = Span>,
1424) -> Option<(usize, usize)> {
1425    let (mut start, mut end) = (None, None);
1426    for (i, span) in body.enumerate() {
1427        let Some(leading) = comments.get_leading(span.lo) else {
1428            continue;
1429        };
1430        for comment in leading {
1431            if comment.text == EARLY_HOIST_START {
1432                start = Some(i);
1433            } else if comment.text == EARLY_HOIST_END {
1434                end = Some(i);
1435            }
1436        }
1437    }
1438    Some((start?, end?))
1439}
1440
1441/// Merges multiple Ecmascript modules into a single AST, setting the syntax contexts correctly so
1442/// that imports work.
1443///
1444/// In `contents`, each import from another module in the group must have an Ident with
1445/// - a `ctxt` listed in scope_hoisting_syntax_contexts.module_contexts, and
1446/// - `sym` being the name of the import.
1447///
1448/// This is then used to map back to the variable name and context of the exporting module.
1449#[instrument(level = Level::TRACE, skip_all, name = "merge")]
1450#[allow(clippy::type_complexity)]
1451async fn merge_modules(
1452    mut contents: Vec<(ResolvedVc<Box<dyn EcmascriptChunkPlaceable>>, CodeGenResult)>,
1453    entry_points: &Vec<(ResolvedVc<Box<dyn EcmascriptChunkPlaceable>>, usize)>,
1454    globals_merged: &'_ Globals,
1455) -> Result<(
1456    Program,
1457    Vec<CodeGenResultComments>,
1458    Vec<CodeGenResultSourceMap>,
1459    SmallVec<[ResolvedVc<Box<dyn GenerateSourceMap>>; 1]>,
1460    Arc<Mutex<Vec<ModulePosition>>>,
1461)> {
1462    struct SetSyntaxContextVisitor<'a> {
1463        modules_header_width: u32,
1464        current_module: ResolvedVc<Box<dyn EcmascriptChunkPlaceable>>,
1465        current_module_idx: u32,
1466        lookup_table: &'a mut Vec<ModulePosition>,
1467        /// The export syntax contexts in the current AST, which will be mapped to merged_ctxts
1468        reverse_module_contexts:
1469            FxHashMap<SyntaxContext, ResolvedVc<Box<dyn EcmascriptChunkPlaceable>>>,
1470        /// For a given module, the `eval_context.imports.exports_ids`. So for a given export, this
1471        /// allows looking up the corresponding local binding's name and context.
1472        export_contexts: &'a FxHashMap<
1473            ResolvedVc<Box<dyn EcmascriptChunkPlaceable>>,
1474            &'a FxHashMap<RcStr, (Id, Span)>,
1475        >,
1476        /// A fresh global SyntaxContext for each module-local context, so that we can merge them
1477        /// into a single global AST.
1478        unique_contexts_cache: &'a mut FxHashMap<
1479            (ResolvedVc<Box<dyn EcmascriptChunkPlaceable>>, SyntaxContext),
1480            SyntaxContext,
1481        >,
1482
1483        error: anyhow::Result<()>,
1484    }
1485
1486    impl<'a> SetSyntaxContextVisitor<'a> {
1487        fn get_context_for(
1488            &mut self,
1489            module: ResolvedVc<Box<dyn EcmascriptChunkPlaceable>>,
1490            local_ctxt: SyntaxContext,
1491        ) -> SyntaxContext {
1492            if let Some(&global_ctxt) = self.unique_contexts_cache.get(&(module, local_ctxt)) {
1493                global_ctxt
1494            } else {
1495                let global_ctxt = SyntaxContext::empty().apply_mark(Mark::new());
1496                self.unique_contexts_cache
1497                    .insert((module, local_ctxt), global_ctxt);
1498                global_ctxt
1499            }
1500        }
1501    }
1502
1503    impl VisitMut for SetSyntaxContextVisitor<'_> {
1504        fn visit_mut_ident(&mut self, ident: &mut Ident) {
1505            let Ident {
1506                sym, ctxt, span, ..
1507            } = ident;
1508
1509            // If this ident is an imported binding, rewrite the name and context to the
1510            // corresponding export in the module that exports it.
1511            if let Some(&module) = self.reverse_module_contexts.get(ctxt) {
1512                let eval_context_exports = self.export_contexts.get(&module).unwrap();
1513                // TODO looking up an Atom in a Map<RcStr, _>, would ideally work without creating a
1514                // RcStr every time.
1515                let sym_rc_str: RcStr = sym.as_str().into();
1516                let (local, local_ctxt) = if let Some(((local, local_ctxt), _)) =
1517                    eval_context_exports.get(&sym_rc_str)
1518                {
1519                    (Some(local), *local_ctxt)
1520                } else if sym.starts_with("__TURBOPACK__imported__module__") {
1521                    // The variable corresponding to the `export * as foo from "...";` is generated
1522                    // in the module generating the reexport (and it's not listed in the
1523                    // eval_context). `EsmAssetReference::code_gen` uses a dummy span when
1524                    // generating this variable.
1525                    (None, SyntaxContext::empty())
1526                } else {
1527                    self.error = Err(anyhow::anyhow!(
1528                        "Expected to find a local export for {sym} with ctxt {ctxt:#?} in \
1529                         {eval_context_exports:?}",
1530                    ));
1531                    return;
1532                };
1533
1534                let global_ctxt = self.get_context_for(module, local_ctxt);
1535
1536                if let Some(local) = local {
1537                    *sym = local.clone();
1538                }
1539                *ctxt = global_ctxt;
1540                span.visit_mut_with(self);
1541            } else {
1542                ident.visit_mut_children_with(self);
1543            }
1544        }
1545
1546        fn visit_mut_syntax_context(&mut self, local_ctxt: &mut SyntaxContext) {
1547            // The modules have their own local syntax contexts, which needs to be mapped to
1548            // contexts that were actually created in the merged Globals.
1549            let module = self
1550                .reverse_module_contexts
1551                .get(local_ctxt)
1552                .copied()
1553                .unwrap_or(self.current_module);
1554
1555            let global_ctxt = self.get_context_for(module, *local_ctxt);
1556            *local_ctxt = global_ctxt;
1557        }
1558        fn visit_mut_span(&mut self, span: &mut Span) {
1559            // Encode the module index into the span, to be able to retrieve the module later for
1560            // finding the correct Comments and SourceMap.
1561            span.lo = CodeGenResultComments::encode_bytepos_with_vec(
1562                self.modules_header_width,
1563                self.current_module_idx,
1564                span.lo,
1565                self.lookup_table,
1566            )
1567            .unwrap_or_else(|err| {
1568                self.error = Err(err);
1569                span.lo
1570            });
1571            span.hi = CodeGenResultComments::encode_bytepos_with_vec(
1572                self.modules_header_width,
1573                self.current_module_idx,
1574                span.hi,
1575                self.lookup_table,
1576            )
1577            .unwrap_or_else(|err| {
1578                self.error = Err(err);
1579                span.hi
1580            });
1581        }
1582    }
1583
1584    // Extract programs into a separate mutable list so that `content` doesn't have to be mutably
1585    // borrowed (and `export_contexts` doesn't have to clone).
1586    let mut programs = contents
1587        .iter_mut()
1588        .map(|(_, content)| content.program.take())
1589        .collect::<Vec<_>>();
1590
1591    let export_contexts = contents
1592        .iter()
1593        .map(|(module, content)| {
1594            Ok((
1595                *module,
1596                content
1597                    .scope_hoisting_syntax_contexts
1598                    .as_ref()
1599                    .map(|(_, export_contexts)| export_contexts)
1600                    .context("expected exports contexts")?,
1601            ))
1602        })
1603        .collect::<Result<FxHashMap<_, _>>>()?;
1604
1605    let mut lookup_table = Vec::new();
1606    let result = GLOBALS.set(globals_merged, || {
1607        let _ = tracing::trace_span!("merge inner").entered();
1608        // As an optimization, assume an average number of 5 contexts per module.
1609        let mut unique_contexts_cache =
1610            FxHashMap::with_capacity_and_hasher(contents.len() * 5, Default::default());
1611
1612        let mut merged_prelude = Vec::new();
1613        let mut prepare_module =
1614            |module_count: usize,
1615             current_module_idx: usize,
1616             (module, content): &(ResolvedVc<Box<dyn EcmascriptChunkPlaceable>>, CodeGenResult),
1617             program: &mut Program,
1618             merged_prelude: &mut Vec<ModuleItem>,
1619             lookup_table: &mut Vec<ModulePosition>| {
1620                let _ = tracing::trace_span!("prepare module").entered();
1621                if let CodeGenResult {
1622                    scope_hoisting_syntax_contexts: Some((module_contexts, _)),
1623                    comments: CodeGenResultComments::Single { extra_comments, .. },
1624                    ..
1625                } = content
1626                {
1627                    // The delimiter comments are keyed by the original spans, so this has to happen
1628                    // before the visitor below rewrites them.
1629                    let early_hoisted = match &*program {
1630                        Program::Module(module) => {
1631                            early_hoist_range(extra_comments, module.body.iter().map(|i| i.span()))
1632                        }
1633                        Program::Script(script) => {
1634                            early_hoist_range(extra_comments, script.body.iter().map(|s| s.span()))
1635                        }
1636                    };
1637
1638                    let modules_header_width = module_count.next_power_of_two().trailing_zeros();
1639                    GLOBALS.set(globals_merged, || {
1640                        let mut visitor = SetSyntaxContextVisitor {
1641                            modules_header_width,
1642                            current_module: *module,
1643                            current_module_idx: current_module_idx as u32,
1644                            lookup_table,
1645                            reverse_module_contexts: module_contexts
1646                                .iter()
1647                                .map(|e| (*e.value(), *e.key()))
1648                                .collect(),
1649                            export_contexts: &export_contexts,
1650                            unique_contexts_cache: &mut unique_contexts_cache,
1651                            error: Ok(()),
1652                        };
1653                        program.visit_mut_with(&mut visitor);
1654                        visitor.error
1655                    })?;
1656
1657                    // Move the delimited statements out, dropping the two delimiters themselves.
1658                    if let Some((start, end)) = early_hoisted {
1659                        let mut hoisted: Vec<ModuleItem> = match program {
1660                            Program::Module(module) => module.body.drain(start..=end).collect(),
1661                            Program::Script(script) => script
1662                                .body
1663                                .drain(start..=end)
1664                                .map(ModuleItem::Stmt)
1665                                .collect(),
1666                        };
1667                        hoisted.pop();
1668                        hoisted.remove(0);
1669                        merged_prelude.extend(hoisted);
1670                    }
1671
1672                    Ok(match program.take() {
1673                        Program::Module(module) => Either::Left(module.body.into_iter()),
1674                        // A module without any ModuleItem::ModuleDecl but a
1675                        // SpecifiedModuleType::EcmaScript can still contain a Module::Script.
1676                        Program::Script(script) => {
1677                            Either::Right(script.body.into_iter().map(ModuleItem::Stmt))
1678                        }
1679                    })
1680                } else {
1681                    bail!("Expected scope_hosting_syntax_contexts");
1682                }
1683            };
1684
1685        let mut inserted = FxHashSet::with_capacity_and_hasher(contents.len(), Default::default());
1686        // Start with inserting the entry points, and recursively inline all their imports.
1687        inserted.extend(entry_points.iter().map(|(_, i)| *i));
1688
1689        let mut inserted_imports = FxHashMap::default();
1690
1691        let span = tracing::trace_span!("merge ASTs");
1692        // Replace inserted `__turbopack_merged_esm__(i);` statements with the corresponding
1693        // ith-module.
1694        let mut queue = entry_points
1695            .iter()
1696            .map(|&(_, i)| {
1697                prepare_module(
1698                    contents.len(),
1699                    i,
1700                    &contents[i],
1701                    &mut programs[i],
1702                    &mut merged_prelude,
1703                    &mut lookup_table,
1704                )
1705                .map_err(|err| (i, err))
1706            })
1707            .flatten_ok()
1708            .rev()
1709            .collect::<Result<Vec<_>, _>>()?;
1710        let mut result = vec![];
1711        while let Some(item) = queue.pop() {
1712            if let ModuleItem::Stmt(stmt) = &item {
1713                match stmt {
1714                    Stmt::Expr(ExprStmt { expr, .. }) => {
1715                        if let Expr::Call(CallExpr {
1716                            callee: Callee::Expr(callee),
1717                            args,
1718                            ..
1719                        }) = &**expr
1720                            && callee.is_ident_ref_to("__turbopack_merged_esm__")
1721                        {
1722                            let index =
1723                                args[0].expr.as_lit().unwrap().as_num().unwrap().value as usize;
1724
1725                            // Only insert once, otherwise the module was already executed
1726                            if inserted.insert(index) {
1727                                queue.extend(
1728                                    prepare_module(
1729                                        contents.len(),
1730                                        index,
1731                                        &contents[index],
1732                                        &mut programs[index],
1733                                        &mut merged_prelude,
1734                                        &mut lookup_table,
1735                                    )
1736                                    .map_err(|err| (index, err))?
1737                                    .into_iter()
1738                                    .rev(),
1739                                );
1740                            }
1741                            continue;
1742                        }
1743                    }
1744                    Stmt::Decl(Decl::Var(var)) => {
1745                        if let [decl] = &*var.decls
1746                            && let Some(name) = decl.name.as_ident()
1747                            && name.sym.starts_with("__TURBOPACK__imported__module__")
1748                        {
1749                            // var __TURBOPACK__imported__module__.. = __turbopack_context__.i(..);
1750
1751                            // Even if these imports are not side-effect free, they only execute
1752                            // once, so no need to insert multiple times.
1753                            match inserted_imports.entry(name.sym.clone()) {
1754                                Entry::Occupied(entry) => {
1755                                    // If the import was already inserted, we can skip it. The
1756                                    // variable mapping minifies better but is unfortunately
1757                                    // necessary as the syntax contexts of the two imports are
1758                                    // different.
1759                                    let entry_ctxt = *entry.get();
1760                                    let new = Ident::new(name.sym.clone(), DUMMY_SP, name.ctxt);
1761                                    let old = Ident::new(name.sym.clone(), DUMMY_SP, entry_ctxt);
1762                                    result.push(ModuleItem::Stmt(
1763                                        quote!("var $new = $old;" as Stmt,
1764                                            new: Ident = new,
1765                                            old: Ident = old
1766                                        ),
1767                                    ));
1768                                    continue;
1769                                }
1770                                Entry::Vacant(entry) => {
1771                                    entry.insert(name.ctxt);
1772                                }
1773                            }
1774                        }
1775                    }
1776                    _ => (),
1777                }
1778            }
1779
1780            result.push(item);
1781        }
1782        drop(span);
1783
1784        let span = tracing::trace_span!("hygiene").entered();
1785        let mut merged_ast = Program::Module(swc_core::ecma::ast::Module {
1786            body: merged_prelude.into_iter().chain(result).collect(),
1787            span: DUMMY_SP,
1788            shebang: None,
1789        });
1790        merged_ast.visit_mut_with(&mut swc_core::ecma::transforms::base::hygiene::hygiene());
1791        drop(span);
1792
1793        Ok((merged_ast, inserted))
1794    });
1795
1796    let (merged_ast, inserted) = match result {
1797        Ok(v) => v,
1798        Err((content_idx, err)) => {
1799            return Err(
1800                // ast-grep-ignore: no-context-turbofmt
1801                err.context(turbofmt!("Processing {}", contents[content_idx].0.ident()).await?),
1802            );
1803        }
1804    };
1805
1806    if cfg!(debug_assertions) && inserted.len() != contents.len() {
1807        bail!(
1808            "Not all merged modules were inserted: {:?}",
1809            contents
1810                .iter()
1811                .enumerate()
1812                .map(async |(i, m)| Ok((inserted.contains(&i), m.0.ident().to_string().await?)))
1813                .try_join()
1814                .await?,
1815        );
1816    }
1817
1818    let comments = contents
1819        .iter_mut()
1820        .map(|(_, content)| content.comments.take())
1821        .collect::<Vec<_>>();
1822
1823    let source_maps = contents
1824        .iter_mut()
1825        .map(|(_, content)| std::mem::take(&mut content.source_map))
1826        .collect::<Vec<_>>();
1827
1828    let original_source_maps = contents
1829        .iter_mut()
1830        .flat_map(|(_, content)| match content.original_source_map {
1831            CodeGenResultOriginalSourceMap::ScopeHoisting(_) => unreachable!(
1832                "Didn't expect nested CodeGenResultOriginalSourceMap::ScopeHoisting: {:?}",
1833                content.original_source_map
1834            ),
1835            CodeGenResultOriginalSourceMap::Single(map) => map,
1836        })
1837        .collect();
1838
1839    Ok((
1840        merged_ast,
1841        comments,
1842        source_maps,
1843        original_source_maps,
1844        Arc::new(Mutex::new(lookup_table)),
1845    ))
1846}
1847
1848/// Provides information about the other modules in the current scope hoisting group.
1849///
1850/// Note that this object contains interior mutability to lazily create syntax contexts in
1851/// `get_module_syntax_context`.
1852#[derive(Clone, Copy)]
1853pub enum ScopeHoistingContext<'a> {
1854    Some {
1855        /// The current module when scope hoisting
1856        module: ResolvedVc<Box<dyn EcmascriptChunkPlaceable>>,
1857        /// All modules in the current group, and whether they should expose their exports
1858        modules:
1859            &'a FxIndexMap<ResolvedVc<Box<dyn EcmascriptChunkPlaceable>>, MergeableModuleExposure>,
1860
1861        is_import_mark: Mark,
1862        globals: &'a Arc<Globals>,
1863        // Interior mutability!
1864        module_syntax_contexts_cache:
1865            &'a FxDashMap<ResolvedVc<Box<dyn EcmascriptChunkPlaceable>>, SyntaxContext>,
1866    },
1867    None,
1868}
1869
1870impl<'a> ScopeHoistingContext<'a> {
1871    /// The current module when scope hoisting
1872    pub fn module(&self) -> Option<ResolvedVc<Box<dyn EcmascriptChunkPlaceable>>> {
1873        match self {
1874            ScopeHoistingContext::Some { module, .. } => Some(*module),
1875            ScopeHoistingContext::None => None,
1876        }
1877    }
1878
1879    /// Whether the current module should not expose it's exports into the module cache.
1880    pub fn skip_module_exports(&self) -> bool {
1881        match self {
1882            ScopeHoistingContext::Some {
1883                module, modules, ..
1884            } => match modules.get(module).unwrap() {
1885                MergeableModuleExposure::None => true,
1886                MergeableModuleExposure::Internal | MergeableModuleExposure::External => false,
1887            },
1888            ScopeHoistingContext::None => false,
1889        }
1890    }
1891
1892    /// To import a specifier from another module, apply this context to the Ident
1893    pub fn get_module_syntax_context(
1894        &self,
1895        module: ResolvedVc<Box<dyn EcmascriptChunkPlaceable>>,
1896    ) -> Option<SyntaxContext> {
1897        match self {
1898            ScopeHoistingContext::Some {
1899                modules,
1900                module_syntax_contexts_cache,
1901                globals,
1902                is_import_mark,
1903                ..
1904            } => {
1905                if !modules.contains_key(&module) {
1906                    return None;
1907                }
1908
1909                Some(match module_syntax_contexts_cache.entry(module) {
1910                    dashmap::Entry::Occupied(e) => *e.get(),
1911                    dashmap::Entry::Vacant(e) => {
1912                        let ctxt = GLOBALS.set(globals, || {
1913                            let mark = Mark::fresh(*is_import_mark);
1914                            SyntaxContext::empty()
1915                                .apply_mark(*is_import_mark)
1916                                .apply_mark(mark)
1917                        });
1918
1919                        e.insert(ctxt);
1920                        ctxt
1921                    }
1922                })
1923            }
1924            ScopeHoistingContext::None => None,
1925        }
1926    }
1927
1928    pub fn get_module_index(
1929        &self,
1930        module: ResolvedVc<Box<dyn EcmascriptChunkPlaceable>>,
1931    ) -> Option<usize> {
1932        match self {
1933            ScopeHoistingContext::Some { modules, .. } => modules.get_index_of(&module),
1934            ScopeHoistingContext::None => None,
1935        }
1936    }
1937}
1938
1939struct CodeGenResult {
1940    program: Program,
1941    source_map: CodeGenResultSourceMap,
1942    comments: CodeGenResultComments,
1943    is_esm: bool,
1944    strict: bool,
1945    original_source_map: CodeGenResultOriginalSourceMap,
1946    minify: MinifyType,
1947    #[allow(clippy::type_complexity)]
1948    /// (Map<Module, corresponding context for imports>, `eval_context.imports.exports_ids`)
1949    scope_hoisting_syntax_contexts: Option<(
1950        FxDashMap<ResolvedVc<Box<dyn EcmascriptChunkPlaceable + 'static>>, SyntaxContext>,
1951        FxHashMap<RcStr, (Id, Span)>,
1952    )>,
1953}
1954
1955struct ScopeHoistingOptions<'a> {
1956    module: ResolvedVc<Box<dyn EcmascriptChunkPlaceable>>,
1957    modules: &'a FxIndexMap<ResolvedVc<Box<dyn EcmascriptChunkPlaceable>>, MergeableModuleExposure>,
1958}
1959
1960async fn process_parse_result(
1961    parsed: Option<ResolvedVc<ParseResult>>,
1962    ident: Vc<AssetIdent>,
1963    specified_module_type: SpecifiedModuleType,
1964    generate_source_map: bool,
1965    original_source_map: Option<ResolvedVc<Box<dyn GenerateSourceMap>>>,
1966    minify: MinifyType,
1967    options: Option<&EcmascriptModuleContentOptions>,
1968    scope_hoisting_options: Option<ScopeHoistingOptions<'_>>,
1969) -> Result<CodeGenResult> {
1970    with_consumed_parse_result(
1971        parsed,
1972        async |mut program, source_map, globals, eval_context, comments| -> Result<CodeGenResult> {
1973            let (top_level_mark, is_esm, strict) = eval_context
1974                .as_ref()
1975                .map_either(
1976                    |e| {
1977                        (
1978                            e.top_level_mark,
1979                            e.is_esm(specified_module_type),
1980                            e.imports.strict,
1981                        )
1982                    },
1983                    |e| {
1984                        (
1985                            e.top_level_mark,
1986                            e.is_esm(specified_module_type),
1987                            e.imports.strict,
1988                        )
1989                    },
1990                )
1991                .into_inner();
1992
1993            let (mut code_gens, ast_paths, retain_syntax_context, prepend_ident_comment) =
1994                if let Some(scope_hoisting_options) = scope_hoisting_options {
1995                    let is_import_mark = GLOBALS.set(globals, || Mark::new());
1996
1997                    let module_syntax_contexts_cache = FxDashMap::default();
1998                    let ctx = ScopeHoistingContext::Some {
1999                        module: scope_hoisting_options.module,
2000                        modules: scope_hoisting_options.modules,
2001                        module_syntax_contexts_cache: &module_syntax_contexts_cache,
2002                        is_import_mark,
2003                        globals,
2004                    };
2005                    let (code_gens, ast_paths) = options
2006                        .unwrap()
2007                        .merged_code_gens(
2008                            ctx,
2009                            match &eval_context {
2010                                Either::Left(e) => e,
2011                                Either::Right(e) => e,
2012                            },
2013                        )
2014                        .await?;
2015
2016                    let export_contexts = eval_context
2017                        .map_either(
2018                            |e| Cow::Owned(e.imports.exports_ids),
2019                            |e| Cow::Borrowed(&e.imports.exports_ids),
2020                        )
2021                        .into_inner();
2022                    let preserved_exports =
2023                        match &*scope_hoisting_options.module.get_exports().await? {
2024                            EcmascriptExports::EsmExports(exports) => exports
2025                                .await?
2026                                .exports
2027                                .iter()
2028                                .filter(|(_, e)| matches!(e, export::EsmExport::LocalBinding(_, _)))
2029                                .map(|(name, e)| {
2030                                    if let Some(((sym, ctxt), _)) = export_contexts.get(name) {
2031                                        Ok((sym.clone(), *ctxt))
2032                                    } else {
2033                                        bail!("Couldn't find export {} for binding {:?}", name, e);
2034                                    }
2035                                })
2036                                .collect::<Result<FxHashSet<_>>>()?,
2037                            _ => Default::default(),
2038                        };
2039
2040                    let prepend_ident_comment = if matches!(minify, MinifyType::NoMinify) {
2041                        Some(Comment {
2042                            kind: CommentKind::Line,
2043                            span: DUMMY_SP,
2044                            text: (&*turbofmt!(" MERGED MODULE: {}", ident).await?).into(),
2045                        })
2046                    } else {
2047                        None
2048                    };
2049
2050                    (
2051                        code_gens,
2052                        Some(ast_paths),
2053                        Some((
2054                            is_import_mark,
2055                            module_syntax_contexts_cache,
2056                            preserved_exports,
2057                            export_contexts,
2058                        )),
2059                        prepend_ident_comment,
2060                    )
2061                } else if let Some(options) = options {
2062                    let (code_gens, ast_paths) = options
2063                        .merged_code_gens(
2064                            ScopeHoistingContext::None,
2065                            match &eval_context {
2066                                Either::Left(e) => e,
2067                                Either::Right(e) => e,
2068                            },
2069                        )
2070                        .await?;
2071                    (code_gens, Some(ast_paths), None, None)
2072                } else {
2073                    (vec![], None, None, None)
2074                };
2075
2076            let extra_comments = SwcComments {
2077                leading: Default::default(),
2078                trailing: Default::default(),
2079            };
2080
2081            let early_hoisted_count = process_content_with_code_gens(
2082                &mut program,
2083                globals,
2084                ast_paths.as_deref().map(|p| &p.ast_paths),
2085                &mut code_gens,
2086            );
2087
2088            for comments in code_gens.iter_mut().flat_map(|cg| cg.comments.as_mut()) {
2089                let leading = Arc::unwrap_or_clone(take(&mut comments.leading));
2090                let trailing = Arc::unwrap_or_clone(take(&mut comments.trailing));
2091
2092                for (pos, v) in leading {
2093                    extra_comments.leading.entry(pos).or_default().extend(v);
2094                }
2095
2096                for (pos, v) in trailing {
2097                    extra_comments.trailing.entry(pos).or_default().extend(v);
2098                }
2099            }
2100
2101            GLOBALS.set(globals, || {
2102                // Delimit the early hoisted statements, which `merge_modules` moves in front of
2103                // the merged module this module is part of.
2104                if retain_syntax_context.is_some() && early_hoisted_count > 0 {
2105                    let end = Span::dummy_with_cmt();
2106                    extra_comments.add_leading(end.lo, early_hoist_comment(EARLY_HOIST_END));
2107                    let start = Span::dummy_with_cmt();
2108                    extra_comments.add_leading(start.lo, early_hoist_comment(EARLY_HOIST_START));
2109                    let (end, start) = (
2110                        Stmt::Empty(EmptyStmt { span: end }),
2111                        Stmt::Empty(EmptyStmt { span: start }),
2112                    );
2113                    match &mut program {
2114                        Program::Module(module) => {
2115                            module
2116                                .body
2117                                .insert(early_hoisted_count, ModuleItem::Stmt(end));
2118                            module.body.insert(0, ModuleItem::Stmt(start));
2119                        }
2120                        Program::Script(script) => {
2121                            script.body.insert(early_hoisted_count, end);
2122                            script.body.insert(0, start);
2123                        }
2124                    }
2125                }
2126
2127                if let Some(prepend_ident_comment) = prepend_ident_comment {
2128                    let span = Span::dummy_with_cmt();
2129                    extra_comments.add_leading(span.lo, prepend_ident_comment);
2130                    let stmt = Stmt::Empty(EmptyStmt { span });
2131                    match &mut program {
2132                        Program::Module(module) => module.body.prepend_stmt(ModuleItem::Stmt(stmt)),
2133                        Program::Script(script) => script.body.prepend_stmt(stmt),
2134                    }
2135                }
2136
2137                if let Some((is_import_mark, _, preserved_exports, _)) = &retain_syntax_context {
2138                    program.visit_mut_with(&mut hygiene_rename_only(
2139                        Some(top_level_mark),
2140                        *is_import_mark,
2141                        preserved_exports,
2142                    ));
2143                } else {
2144                    program.visit_mut_with(
2145                        &mut swc_core::ecma::transforms::base::hygiene::hygiene_with_config(
2146                            swc_core::ecma::transforms::base::hygiene::Config {
2147                                top_level_mark,
2148                                ..Default::default()
2149                            },
2150                        ),
2151                    );
2152                }
2153                program.visit_mut_with(&mut swc_core::ecma::transforms::base::fixer::fixer(None));
2154
2155                // we need to remove any shebang before bundling as it's only valid as the first
2156                // line in a js file (not in a chunk item wrapped in the runtime)
2157                remove_shebang(&mut program);
2158                remove_directives(&mut program);
2159            });
2160
2161            Ok(CodeGenResult {
2162                program,
2163                source_map: if generate_source_map {
2164                    CodeGenResultSourceMap::Single {
2165                        source_map: source_map.clone(),
2166                    }
2167                } else {
2168                    CodeGenResultSourceMap::None
2169                },
2170                comments: CodeGenResultComments::Single {
2171                    comments,
2172                    extra_comments,
2173                },
2174                is_esm,
2175                strict,
2176                original_source_map: CodeGenResultOriginalSourceMap::Single(original_source_map),
2177                minify,
2178                scope_hoisting_syntax_contexts: retain_syntax_context
2179                    // TODO ideally don't clone here
2180                    .map(|(_, ctxts, _, export_contexts)| (ctxts, export_contexts.into_owned())),
2181            })
2182        },
2183        async |parse_result| -> Result<CodeGenResult> {
2184            Ok(match parse_result {
2185                ParseResult::Ok { .. } => unreachable!(),
2186                ParseResult::Unparsable { messages } => {
2187                    let error_messages = messages
2188                        .as_ref()
2189                        .and_then(|m| m.first().map(|f| format!("\n{f}")))
2190                        .unwrap_or("".into());
2191                    let msg = &*turbofmt!(
2192                        "Could not parse module '{}'\n{error_messages}",
2193                        ident.await?.path
2194                    )
2195                    .await?;
2196                    let body = vec![
2197                        quote!(
2198                            "var e = new Error($msg);" as Stmt,
2199                            msg: Expr = Expr::Lit(msg.into()),
2200                        ),
2201                        quote!("e.code = 'MODULE_UNPARSABLE';" as Stmt),
2202                        quote!("throw e;" as Stmt),
2203                    ];
2204
2205                    CodeGenResult {
2206                        program: Program::Script(Script {
2207                            span: DUMMY_SP,
2208                            body,
2209                            shebang: None,
2210                        }),
2211                        source_map: CodeGenResultSourceMap::None,
2212                        comments: CodeGenResultComments::Empty,
2213                        is_esm: false,
2214                        strict: false,
2215                        original_source_map: CodeGenResultOriginalSourceMap::Single(None),
2216                        minify: MinifyType::NoMinify,
2217                        scope_hoisting_syntax_contexts: None,
2218                    }
2219                }
2220                ParseResult::NotFound => {
2221                    let msg = &*turbofmt!(
2222                        "Could not parse module '{}', file not found",
2223                        ident.await?.path
2224                    )
2225                    .await?;
2226                    let body = vec![
2227                        quote!(
2228                            "var e = new Error($msg);" as Stmt,
2229                            msg: Expr = Expr::Lit(msg.into()),
2230                        ),
2231                        quote!("e.code = 'MODULE_UNPARSABLE';" as Stmt),
2232                        quote!("throw e;" as Stmt),
2233                    ];
2234                    CodeGenResult {
2235                        program: Program::Script(Script {
2236                            span: DUMMY_SP,
2237                            body,
2238                            shebang: None,
2239                        }),
2240                        source_map: CodeGenResultSourceMap::None,
2241                        comments: CodeGenResultComments::Empty,
2242                        is_esm: false,
2243                        strict: false,
2244                        original_source_map: CodeGenResultOriginalSourceMap::Single(None),
2245                        minify: MinifyType::NoMinify,
2246                        scope_hoisting_syntax_contexts: None,
2247                    }
2248                }
2249            })
2250        },
2251    )
2252    .instrument(tracing::trace_span!(
2253        "process parse result",
2254        ident = display(ident.to_string().await?),
2255    ))
2256    .await
2257}
2258
2259/// Try to avoid cloning the AST and Globals by unwrapping the ReadRef (and cloning otherwise).
2260async fn with_consumed_parse_result<T>(
2261    parsed: Option<ResolvedVc<ParseResult>>,
2262    success: impl AsyncFnOnce(
2263        Program,
2264        &Arc<SourceMap>,
2265        &Arc<Globals>,
2266        Either<EvalContext, &'_ EvalContext>,
2267        Either<ImmutableComments, Arc<ImmutableComments>>,
2268    ) -> Result<T>,
2269    error: impl AsyncFnOnce(&ParseResult) -> Result<T>,
2270) -> Result<T> {
2271    let Some(parsed) = parsed else {
2272        let globals = Globals::new();
2273        let eval_context = GLOBALS.set(&globals, || EvalContext {
2274            unresolved_mark: Mark::new(),
2275            top_level_mark: Mark::new(),
2276            imports: Default::default(),
2277            force_free_values: Default::default(),
2278        });
2279        return success(
2280            Program::Module(swc_core::ecma::ast::Module::dummy()),
2281            &Default::default(),
2282            &Default::default(),
2283            Either::Left(eval_context),
2284            Either::Left(Default::default()),
2285        )
2286        .await;
2287    };
2288
2289    let parsed = parsed.final_read_hint().await?;
2290    match &*parsed {
2291        ParseResult::Ok { .. } => {
2292            let mut parsed = ReadRef::try_unwrap(parsed);
2293            let (program, source_map, globals, eval_context, comments) = match &mut parsed {
2294                Ok(ParseResult::Ok {
2295                    program,
2296                    source_map,
2297                    globals,
2298                    eval_context,
2299                    comments,
2300                    ..
2301                }) => (
2302                    program.take(),
2303                    &*source_map,
2304                    &*globals,
2305                    Either::Left(std::mem::replace(
2306                        eval_context,
2307                        EvalContext {
2308                            unresolved_mark: eval_context.unresolved_mark,
2309                            top_level_mark: eval_context.top_level_mark,
2310                            imports: Default::default(),
2311                            force_free_values: Default::default(),
2312                        },
2313                    )),
2314                    match Arc::try_unwrap(take(comments)) {
2315                        Ok(comments) => Either::Left(comments),
2316                        Err(comments) => Either::Right(comments),
2317                    },
2318                ),
2319                Err(parsed) => {
2320                    let ParseResult::Ok {
2321                        program,
2322                        source_map,
2323                        globals,
2324                        eval_context,
2325                        comments,
2326                        ..
2327                    } = &**parsed
2328                    else {
2329                        unreachable!();
2330                    };
2331                    (
2332                        program.clone(),
2333                        source_map,
2334                        globals,
2335                        Either::Right(eval_context),
2336                        Either::Right(comments.clone()),
2337                    )
2338                }
2339                _ => unreachable!(),
2340            };
2341
2342            success(program, source_map, globals, eval_context, comments).await
2343        }
2344        _ => error(&parsed).await,
2345    }
2346}
2347
2348async fn emit_content(
2349    content: CodeGenResult,
2350    additional_ids: SmallVec<[ModuleId; 1]>,
2351) -> Result<Vc<EcmascriptModuleContent>> {
2352    let CodeGenResult {
2353        program,
2354        source_map,
2355        comments,
2356        is_esm,
2357        strict,
2358        original_source_map,
2359        minify,
2360        scope_hoisting_syntax_contexts: _,
2361    } = content;
2362
2363    let generate_source_map = source_map.is_some();
2364
2365    // Collect identifier names for source maps before emitting
2366    let source_map_names = if generate_source_map {
2367        let mut collector = IdentCollector::default();
2368        program.visit_with(&mut collector);
2369        collector.into_map()
2370    } else {
2371        Default::default()
2372    };
2373
2374    let mut bytes: Vec<u8> = vec![];
2375    // TODO: Insert this as a sourceless segment so that sourcemaps aren't affected.
2376    // = format!("/* {} */\n", self.module.path().to_string().await?).into_bytes();
2377
2378    let mut mappings = vec![];
2379
2380    let source_map = Arc::new(source_map);
2381
2382    {
2383        let mut wr = JsWriter::new(
2384            // unused anyway?
2385            Default::default(),
2386            "\n",
2387            &mut bytes,
2388            generate_source_map.then_some(&mut mappings),
2389        );
2390        if matches!(minify, MinifyType::Minify { .. }) {
2391            wr.set_indent_str("");
2392        }
2393
2394        let comments = comments.consumable();
2395
2396        let mut emitter = Emitter {
2397            cfg: swc_core::ecma::codegen::Config::default(),
2398            cm: source_map.clone(),
2399            comments: Some(&comments as &dyn Comments),
2400            wr,
2401        };
2402
2403        emitter.emit_program(&program)?;
2404        // Drop the AST eagerly so we don't keep it in memory while generating source maps
2405        drop(program);
2406    }
2407
2408    let source_map = if generate_source_map {
2409        let original_source_maps = original_source_map
2410            .iter()
2411            .map(|map| map.generate_source_map())
2412            .try_join()
2413            .await?;
2414        let original_source_maps = original_source_maps
2415            .iter()
2416            .filter_map(|map| map.as_content())
2417            .map(|map| map.content())
2418            .collect::<Vec<_>>();
2419
2420        Some(generate_js_source_map(
2421            &*source_map,
2422            mappings,
2423            original_source_maps,
2424            matches!(
2425                original_source_map,
2426                CodeGenResultOriginalSourceMap::Single(_)
2427            ),
2428            true,
2429            source_map_names,
2430        )?)
2431    } else {
2432        None
2433    };
2434
2435    Ok(EcmascriptModuleContent {
2436        inner_code: bytes.into(),
2437        source_map,
2438        is_esm,
2439        strict,
2440        additional_ids,
2441    }
2442    .cell())
2443}
2444
2445/// Applies the code generations, returning the number of early hoisted statements it prepended.
2446#[instrument(level = Level::TRACE, skip_all, name = "apply code generation")]
2447fn process_content_with_code_gens(
2448    program: &mut Program,
2449    globals: &Globals,
2450    trie: Option<&AstPathTrie>,
2451    code_gens: &mut Vec<CodeGeneration>,
2452) -> usize {
2453    let mut visitors = Vec::new();
2454    let mut root_visitors = Vec::new();
2455    let mut early_hoisted_stmts = FxIndexMap::default();
2456    let mut hoisted_stmts = FxIndexMap::default();
2457    let mut early_late_stmts = FxIndexMap::default();
2458    let mut late_stmts = FxIndexMap::default();
2459    for code_gen in code_gens {
2460        for CodeGenerationHoistedStmt { key, stmt } in code_gen.hoisted_stmts.drain(..) {
2461            hoisted_stmts.entry(key).or_insert(stmt);
2462        }
2463        for CodeGenerationHoistedStmt { key, stmt } in code_gen.early_hoisted_stmts.drain(..) {
2464            early_hoisted_stmts.insert(key.clone(), stmt);
2465        }
2466        for CodeGenerationHoistedStmt { key, stmt } in code_gen.late_stmts.drain(..) {
2467            late_stmts.insert(key.clone(), stmt);
2468        }
2469        for CodeGenerationHoistedStmt { key, stmt } in code_gen.early_late_stmts.drain(..) {
2470            early_late_stmts.insert(key.clone(), stmt);
2471        }
2472        for (path, visitor) in &code_gen.visitors {
2473            if path.is_root() {
2474                root_visitors.push(&**visitor);
2475            } else {
2476                visitors.push((*path, &**visitor));
2477            }
2478        }
2479    }
2480
2481    GLOBALS.set(globals, || {
2482        if !visitors.is_empty() {
2483            let trie = trie.expect("code gens with visitors always come with their trie");
2484            let visitors = Visitors::new(trie, visitors);
2485            if !visitors.is_empty() {
2486                program.visit_mut_with_ast_path(
2487                    &mut ApplyVisitors::new(&visitors),
2488                    &mut Default::default(),
2489                );
2490            }
2491        }
2492        for pass in root_visitors {
2493            program.modify(pass);
2494        }
2495    });
2496
2497    let early_hoisted_count = early_hoisted_stmts.len();
2498    match program {
2499        Program::Module(ast::Module { body, .. }) => {
2500            body.splice(
2501                0..0,
2502                early_hoisted_stmts
2503                    .into_values()
2504                    .chain(hoisted_stmts.into_values())
2505                    .map(ModuleItem::Stmt),
2506            );
2507            body.extend(
2508                early_late_stmts
2509                    .into_values()
2510                    .chain(late_stmts.into_values())
2511                    .map(ModuleItem::Stmt),
2512            );
2513        }
2514        Program::Script(Script { body, .. }) => {
2515            body.splice(
2516                0..0,
2517                early_hoisted_stmts
2518                    .into_values()
2519                    .chain(hoisted_stmts.into_values()),
2520            );
2521            body.extend(
2522                early_late_stmts
2523                    .into_values()
2524                    .chain(late_stmts.into_values()),
2525            );
2526        }
2527    };
2528    early_hoisted_count
2529}
2530
2531/// Like `hygiene`, but only renames the Atoms without clearing all SyntaxContexts
2532///
2533/// Don't rename idents marked with `is_import_mark` (i.e. a reference to a value which is imported
2534/// from another merged module) or listed in `preserve_exports` (i.e. an exported local binding):
2535/// even if they are causing collisions, they will be handled by the next hygiene pass over the
2536/// whole module.
2537fn hygiene_rename_only(
2538    top_level_mark: Option<Mark>,
2539    is_import_mark: Mark,
2540    preserved_exports: &FxHashSet<Id>,
2541) -> impl VisitMut {
2542    struct HygieneRenamer<'a> {
2543        preserved_exports: &'a FxHashSet<Id>,
2544        is_import_mark: Mark,
2545    }
2546    // Copied from `hygiene_with_config`'s HygieneRenamer, but added an `preserved_exports`
2547    impl swc_core::ecma::transforms::base::rename::Renamer for HygieneRenamer<'_> {
2548        type Target = Id;
2549
2550        const MANGLE: bool = false;
2551        const RESET_N: bool = true;
2552
2553        fn new_name_for(&self, orig: &Id, n: &mut usize) -> Atom {
2554            let res = if *n == 0 {
2555                orig.0.clone()
2556            } else {
2557                format!("{}{}", orig.0, n).into()
2558            };
2559            *n += 1;
2560            res
2561        }
2562
2563        fn preserve_name(&self, orig: &Id) -> bool {
2564            self.preserved_exports.contains(orig) || orig.1.has_mark(self.is_import_mark)
2565        }
2566    }
2567    swc_core::ecma::transforms::base::rename::renamer_keep_contexts(
2568        swc_core::ecma::transforms::base::hygiene::Config {
2569            top_level_mark: top_level_mark.unwrap_or_default(),
2570            ..Default::default()
2571        },
2572        HygieneRenamer {
2573            preserved_exports,
2574            is_import_mark,
2575        },
2576    )
2577}
2578
2579#[derive(Default)]
2580enum CodeGenResultSourceMap {
2581    #[default]
2582    /// No source map should be generated for this module
2583    None,
2584    Single {
2585        source_map: Arc<SourceMap>,
2586    },
2587    ScopeHoisting {
2588        /// The bitwidth of the modules header in the spans, see
2589        /// [CodeGenResultComments::encode_bytepos]
2590        modules_header_width: u32,
2591        lookup_table: Arc<Mutex<Vec<ModulePosition>>>,
2592        source_maps: Vec<CodeGenResultSourceMap>,
2593    },
2594}
2595
2596impl CodeGenResultSourceMap {
2597    fn is_some(&self) -> bool {
2598        match self {
2599            CodeGenResultSourceMap::None => false,
2600            CodeGenResultSourceMap::Single { .. }
2601            | CodeGenResultSourceMap::ScopeHoisting { .. } => true,
2602        }
2603    }
2604}
2605
2606impl Debug for CodeGenResultSourceMap {
2607    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2608        match self {
2609            CodeGenResultSourceMap::None => write!(f, "CodeGenResultSourceMap::None"),
2610            CodeGenResultSourceMap::Single { source_map } => {
2611                write!(
2612                    f,
2613                    "CodeGenResultSourceMap::Single {{ source_map: {:?} }}",
2614                    source_map.files().clone()
2615                )
2616            }
2617            CodeGenResultSourceMap::ScopeHoisting {
2618                modules_header_width,
2619                source_maps,
2620                ..
2621            } => write!(
2622                f,
2623                "CodeGenResultSourceMap::ScopeHoisting {{ modules_header_width: \
2624                 {modules_header_width}, source_maps: {source_maps:?} }}",
2625            ),
2626        }
2627    }
2628}
2629
2630impl Files for CodeGenResultSourceMap {
2631    fn try_lookup_source_file(
2632        &self,
2633        pos: BytePos,
2634    ) -> Result<Option<Arc<SourceFile>>, SourceMapLookupError> {
2635        match self {
2636            CodeGenResultSourceMap::None => Ok(None),
2637            CodeGenResultSourceMap::Single { source_map } => source_map.try_lookup_source_file(pos),
2638            CodeGenResultSourceMap::ScopeHoisting {
2639                modules_header_width,
2640                lookup_table,
2641                source_maps,
2642            } => {
2643                let (module, pos) =
2644                    CodeGenResultComments::decode_bytepos(*modules_header_width, pos, lookup_table);
2645                source_maps[module].try_lookup_source_file(pos)
2646            }
2647        }
2648    }
2649
2650    fn is_in_file(&self, f: &Arc<SourceFile>, raw_pos: BytePos) -> bool {
2651        match self {
2652            CodeGenResultSourceMap::None => false,
2653            CodeGenResultSourceMap::Single { .. } => f.start_pos <= raw_pos && raw_pos < f.end_pos,
2654            CodeGenResultSourceMap::ScopeHoisting { .. } => {
2655                // let (module, pos) = CodeGenResultComments::decode_bytepos(*modules_header_width,
2656                // pos);
2657
2658                // TODO optimize this, unfortunately, `SourceFile` doesn't know which `module` it
2659                // belongs from.
2660                false
2661            }
2662        }
2663    }
2664
2665    fn map_raw_pos(&self, pos: BytePos) -> BytePos {
2666        match self {
2667            CodeGenResultSourceMap::None => BytePos::DUMMY,
2668            CodeGenResultSourceMap::Single { .. } => pos,
2669            CodeGenResultSourceMap::ScopeHoisting {
2670                modules_header_width,
2671                lookup_table,
2672                ..
2673            } => CodeGenResultComments::decode_bytepos(*modules_header_width, pos, lookup_table).1,
2674        }
2675    }
2676}
2677
2678impl SourceMapper for CodeGenResultSourceMap {
2679    fn lookup_char_pos(&self, pos: BytePos) -> Loc {
2680        match self {
2681            CodeGenResultSourceMap::None => {
2682                panic!("CodeGenResultSourceMap::None cannot lookup_char_pos")
2683            }
2684            CodeGenResultSourceMap::Single { source_map } => source_map.lookup_char_pos(pos),
2685            CodeGenResultSourceMap::ScopeHoisting {
2686                modules_header_width,
2687                lookup_table,
2688                source_maps,
2689            } => {
2690                let (module, pos) =
2691                    CodeGenResultComments::decode_bytepos(*modules_header_width, pos, lookup_table);
2692                source_maps[module].lookup_char_pos(pos)
2693            }
2694        }
2695    }
2696    fn span_to_lines(&self, sp: Span) -> FileLinesResult {
2697        match self {
2698            CodeGenResultSourceMap::None => {
2699                panic!("CodeGenResultSourceMap::None cannot span_to_lines")
2700            }
2701            CodeGenResultSourceMap::Single { source_map } => source_map.span_to_lines(sp),
2702            CodeGenResultSourceMap::ScopeHoisting {
2703                modules_header_width,
2704                lookup_table,
2705                source_maps,
2706            } => {
2707                let (module, lo) = CodeGenResultComments::decode_bytepos(
2708                    *modules_header_width,
2709                    sp.lo,
2710                    lookup_table,
2711                );
2712                source_maps[module].span_to_lines(Span {
2713                    lo,
2714                    hi: CodeGenResultComments::decode_bytepos(
2715                        *modules_header_width,
2716                        sp.hi,
2717                        lookup_table,
2718                    )
2719                    .1,
2720                })
2721            }
2722        }
2723    }
2724    fn span_to_string(&self, sp: Span) -> String {
2725        match self {
2726            CodeGenResultSourceMap::None => {
2727                panic!("CodeGenResultSourceMap::None cannot span_to_string")
2728            }
2729            CodeGenResultSourceMap::Single { source_map } => source_map.span_to_string(sp),
2730            CodeGenResultSourceMap::ScopeHoisting {
2731                modules_header_width,
2732                lookup_table,
2733                source_maps,
2734            } => {
2735                let (module, lo) = CodeGenResultComments::decode_bytepos(
2736                    *modules_header_width,
2737                    sp.lo,
2738                    lookup_table,
2739                );
2740                source_maps[module].span_to_string(Span {
2741                    lo,
2742                    hi: CodeGenResultComments::decode_bytepos(
2743                        *modules_header_width,
2744                        sp.hi,
2745                        lookup_table,
2746                    )
2747                    .1,
2748                })
2749            }
2750        }
2751    }
2752    fn span_to_filename(&self, sp: Span) -> Arc<FileName> {
2753        match self {
2754            CodeGenResultSourceMap::None => {
2755                panic!("CodeGenResultSourceMap::None cannot span_to_filename")
2756            }
2757            CodeGenResultSourceMap::Single { source_map } => source_map.span_to_filename(sp),
2758            CodeGenResultSourceMap::ScopeHoisting {
2759                modules_header_width,
2760                lookup_table,
2761                source_maps,
2762            } => {
2763                let (module, lo) = CodeGenResultComments::decode_bytepos(
2764                    *modules_header_width,
2765                    sp.lo,
2766                    lookup_table,
2767                );
2768                source_maps[module].span_to_filename(Span {
2769                    lo,
2770                    hi: CodeGenResultComments::decode_bytepos(
2771                        *modules_header_width,
2772                        sp.hi,
2773                        lookup_table,
2774                    )
2775                    .1,
2776                })
2777            }
2778        }
2779    }
2780    fn merge_spans(&self, sp_lhs: Span, sp_rhs: Span) -> Option<Span> {
2781        match self {
2782            CodeGenResultSourceMap::None => {
2783                panic!("CodeGenResultSourceMap::None cannot merge_spans")
2784            }
2785            CodeGenResultSourceMap::Single { source_map } => source_map.merge_spans(sp_lhs, sp_rhs),
2786            CodeGenResultSourceMap::ScopeHoisting {
2787                modules_header_width,
2788                lookup_table,
2789                source_maps,
2790            } => {
2791                let (module_lhs, lo_lhs) = CodeGenResultComments::decode_bytepos(
2792                    *modules_header_width,
2793                    sp_lhs.lo,
2794                    lookup_table,
2795                );
2796                let (module_rhs, lo_rhs) = CodeGenResultComments::decode_bytepos(
2797                    *modules_header_width,
2798                    sp_rhs.lo,
2799                    lookup_table,
2800                );
2801                if module_lhs != module_rhs {
2802                    return None;
2803                }
2804                source_maps[module_lhs].merge_spans(
2805                    Span {
2806                        lo: lo_lhs,
2807                        hi: CodeGenResultComments::decode_bytepos(
2808                            *modules_header_width,
2809                            sp_lhs.hi,
2810                            lookup_table,
2811                        )
2812                        .1,
2813                    },
2814                    Span {
2815                        lo: lo_rhs,
2816                        hi: CodeGenResultComments::decode_bytepos(
2817                            *modules_header_width,
2818                            sp_rhs.hi,
2819                            lookup_table,
2820                        )
2821                        .1,
2822                    },
2823                )
2824            }
2825        }
2826    }
2827    fn call_span_if_macro(&self, sp: Span) -> Span {
2828        match self {
2829            CodeGenResultSourceMap::None => {
2830                panic!("CodeGenResultSourceMap::None cannot call_span_if_macro")
2831            }
2832            CodeGenResultSourceMap::Single { source_map } => source_map.call_span_if_macro(sp),
2833            CodeGenResultSourceMap::ScopeHoisting {
2834                modules_header_width,
2835                lookup_table,
2836                source_maps,
2837            } => {
2838                let (module, lo) = CodeGenResultComments::decode_bytepos(
2839                    *modules_header_width,
2840                    sp.lo,
2841                    lookup_table,
2842                );
2843                source_maps[module].call_span_if_macro(Span {
2844                    lo,
2845                    hi: CodeGenResultComments::decode_bytepos(
2846                        *modules_header_width,
2847                        sp.hi,
2848                        lookup_table,
2849                    )
2850                    .1,
2851                })
2852            }
2853        }
2854    }
2855    fn doctest_offset_line(&self, _line: usize) -> usize {
2856        panic!("doctest_offset_line is not implemented for CodeGenResultSourceMap");
2857    }
2858    fn span_to_snippet(&self, sp: Span) -> Result<String, Box<SpanSnippetError>> {
2859        match self {
2860            CodeGenResultSourceMap::None => Err(Box::new(SpanSnippetError::SourceNotAvailable {
2861                filename: FileName::Anon,
2862            })),
2863            CodeGenResultSourceMap::Single { source_map } => source_map.span_to_snippet(sp),
2864            CodeGenResultSourceMap::ScopeHoisting {
2865                modules_header_width,
2866                lookup_table,
2867                source_maps,
2868            } => {
2869                let (module, lo) = CodeGenResultComments::decode_bytepos(
2870                    *modules_header_width,
2871                    sp.lo,
2872                    lookup_table,
2873                );
2874                source_maps[module].span_to_snippet(Span {
2875                    lo,
2876                    hi: CodeGenResultComments::decode_bytepos(
2877                        *modules_header_width,
2878                        sp.hi,
2879                        lookup_table,
2880                    )
2881                    .1,
2882                })
2883            }
2884        }
2885    }
2886    fn map_raw_pos(&self, pos: BytePos) -> BytePos {
2887        match self {
2888            CodeGenResultSourceMap::None => BytePos::DUMMY,
2889            CodeGenResultSourceMap::Single { .. } => pos,
2890            CodeGenResultSourceMap::ScopeHoisting {
2891                modules_header_width,
2892                lookup_table,
2893                ..
2894            } => CodeGenResultComments::decode_bytepos(*modules_header_width, pos, lookup_table).1,
2895        }
2896    }
2897}
2898impl SourceMapperExt for CodeGenResultSourceMap {
2899    fn get_code_map(&self) -> &dyn SourceMapper {
2900        self
2901    }
2902}
2903
2904#[derive(Debug)]
2905enum CodeGenResultOriginalSourceMap {
2906    Single(Option<ResolvedVc<Box<dyn GenerateSourceMap>>>),
2907    ScopeHoisting(SmallVec<[ResolvedVc<Box<dyn GenerateSourceMap>>; 1]>),
2908}
2909
2910impl CodeGenResultOriginalSourceMap {
2911    fn iter(&self) -> impl Iterator<Item = ResolvedVc<Box<dyn GenerateSourceMap>>> {
2912        match self {
2913            CodeGenResultOriginalSourceMap::Single(map) => Either::Left(map.iter().copied()),
2914            CodeGenResultOriginalSourceMap::ScopeHoisting(maps) => {
2915                Either::Right(maps.iter().copied())
2916            }
2917        }
2918    }
2919}
2920
2921/// Stores a module index in position 0 and the full byte position of the source map in position 1
2922struct ModulePosition(u32, u32);
2923
2924enum CodeGenResultComments {
2925    Single {
2926        comments: Either<ImmutableComments, Arc<ImmutableComments>>,
2927        extra_comments: SwcComments,
2928    },
2929    ScopeHoisting {
2930        /// The bitwidth of the modules header in the spans, see
2931        /// [CodeGenResultComments::encode_bytepos]
2932        modules_header_width: u32,
2933        lookup_table: Arc<Mutex<Vec<ModulePosition>>>,
2934        comments: Vec<CodeGenResultComments>,
2935    },
2936    Empty,
2937}
2938
2939unsafe impl Send for CodeGenResultComments {}
2940unsafe impl Sync for CodeGenResultComments {}
2941
2942impl CodeGenResultComments {
2943    const CONTINUATION_BIT: u32 = 1 << 31;
2944    const SIGN_EXTENSION_BIT: u32 = 1 << 30;
2945
2946    #[inline]
2947    fn encode_bytepos_impl(
2948        modules_header_width: u32,
2949        module: u32,
2950        pos: BytePos,
2951        push_into_lookup: &mut impl FnMut(u32, u32) -> Result<u32>,
2952    ) -> Result<BytePos> {
2953        if pos.is_dummy() {
2954            // nothing to encode
2955            return Ok(pos);
2956        }
2957
2958        // Bit layout for encoded BytePos (32 bits):
2959        // [31] Continuation bit. If set (1), the remaining 31 bits [0..30] encode an index into
2960        //      the lookup vector where (module, original_bytepos) is stored.
2961        //      In this case, decoding ignores other fields and fetches from the table.
2962        // If not set (0):
2963        // [30] Sign-extend bit. Indicates whether the stolen high bits of the original bytepos
2964        //      were all 1s (1) or all 0s (0), so that decoding can restore the original high bits.
2965        // [30 - modules_header_width + 1 .. 30) Module id: modules_header_width bits immediately
2966        //      below the sign-extend bit.
2967        // [0 .. (32 - (2 + modules_header_width)) ) Remaining low bits store the truncated bytepos.
2968        //
2969        // Notes:
2970        // - We reserve 2 header bits always (continuation + sign-extend), so header_width =
2971        //   modules_header_width + 2, and pos_width = 32 - header_width.
2972        // - When the original value does not fit in the available pos_width with a uniform high bit
2973        //   pattern, we spill (set continuation) and store (module, pos) in the lookup table and
2974        //   encode the index with the continuation bit set.
2975        //
2976        // Example (diagrammatic only):
2977        // modules_header_width = 4
2978        // Key:
2979        // (c = continuation, s = sign-extend, m = module, p = pos bits, i = lookup table index)
2980        //
2981        // The continuation bit is set, and the remaining 31 bits are reinterpreted as the index
2982        // into the lookup table.
2983        // Bytes: 1iii iiii iiii iiii iiii iiii iiii iiii
2984        //
2985        // The continuation bit is not set,
2986        // Bytes: 0smm mmpp pppp pppp pppp pppp pppp pppp
2987
2988        let header_width = modules_header_width + 2;
2989        let pos_width = 32 - header_width;
2990
2991        let pos = pos.0;
2992
2993        let old_high_bits = pos >> pos_width;
2994        let high_bits_set = if (2u32.pow(header_width) - 1) == old_high_bits {
2995            true
2996        } else if old_high_bits == 0 {
2997            false
2998        } else {
2999            // The integer is too large for our desired header width and we need to store the result
3000            // in our vector and set the flag to reinterpret this data as the index of
3001            // the vector where the element is being stored.
3002            let ix = push_into_lookup(module, pos)?;
3003            // Make sure that the index fits within the allotted bits
3004            assert_eq!(ix & CodeGenResultComments::CONTINUATION_BIT, 0);
3005
3006            return Ok(BytePos(ix | CodeGenResultComments::CONTINUATION_BIT));
3007        };
3008
3009        let pos = pos & !((2u32.pow(header_width) - 1) << pos_width);
3010        let encoded_high_bits = if high_bits_set {
3011            CodeGenResultComments::SIGN_EXTENSION_BIT
3012        } else {
3013            0
3014        };
3015        let encoded_module = module << pos_width;
3016
3017        Ok(BytePos(encoded_module | encoded_high_bits | pos))
3018    }
3019
3020    fn take(&mut self) -> Self {
3021        std::mem::replace(self, CodeGenResultComments::Empty)
3022    }
3023
3024    fn consumable(&self) -> CodeGenResultCommentsConsumable<'_> {
3025        match self {
3026            CodeGenResultComments::Single {
3027                comments,
3028                extra_comments,
3029            } => CodeGenResultCommentsConsumable::Single {
3030                comments: match comments {
3031                    Either::Left(comments) => comments.consumable(),
3032                    Either::Right(comments) => comments.consumable(),
3033                },
3034                extra_comments,
3035            },
3036            CodeGenResultComments::ScopeHoisting {
3037                modules_header_width,
3038                lookup_table,
3039                comments,
3040            } => CodeGenResultCommentsConsumable::ScopeHoisting {
3041                modules_header_width: *modules_header_width,
3042                lookup_table: lookup_table.clone(),
3043                comments: comments.iter().map(|c| c.consumable()).collect(),
3044            },
3045            CodeGenResultComments::Empty => CodeGenResultCommentsConsumable::Empty,
3046        }
3047    }
3048
3049    fn encode_bytepos(
3050        modules_header_width: u32,
3051        module: u32,
3052        pos: BytePos,
3053        lookup_table: Arc<Mutex<Vec<ModulePosition>>>,
3054    ) -> Result<BytePos> {
3055        let mut push = |module: u32, pos_u32: u32| -> Result<u32> {
3056            let mut lookup_table = lookup_table
3057                .lock()
3058                .map_err(|_| anyhow!("Failed to grab lock on the index map for byte positions"))?;
3059            let ix = lookup_table.len() as u32;
3060            if ix >= 1 << 30 {
3061                bail!("Too many byte positions being stored");
3062            }
3063            lookup_table.push(ModulePosition(module, pos_u32));
3064            Ok(ix)
3065        };
3066        Self::encode_bytepos_impl(modules_header_width, module, pos, &mut push)
3067    }
3068
3069    fn encode_bytepos_with_vec(
3070        modules_header_width: u32,
3071        module: u32,
3072        pos: BytePos,
3073        lookup_table: &mut Vec<ModulePosition>,
3074    ) -> Result<BytePos> {
3075        let mut push = |module: u32, pos_u32: u32| -> Result<u32> {
3076            let ix = lookup_table.len() as u32;
3077            if ix >= 1 << 30 {
3078                bail!("Too many byte positions being stored");
3079            }
3080            lookup_table.push(ModulePosition(module, pos_u32));
3081            Ok(ix)
3082        };
3083        Self::encode_bytepos_impl(modules_header_width, module, pos, &mut push)
3084    }
3085
3086    fn decode_bytepos(
3087        modules_header_width: u32,
3088        pos: BytePos,
3089        lookup_table: &Mutex<Vec<ModulePosition>>,
3090    ) -> (usize, BytePos) {
3091        if pos.is_dummy() {
3092            // nothing to decode
3093            panic!("Cannot decode dummy BytePos");
3094        }
3095
3096        let header_width = modules_header_width + 2;
3097        let pos_width = 32 - header_width;
3098
3099        if (CodeGenResultComments::CONTINUATION_BIT & pos.0)
3100            == CodeGenResultComments::CONTINUATION_BIT
3101        {
3102            let lookup_table = lookup_table
3103                .lock()
3104                .expect("Failed to grab lock on the index map for byte position");
3105            let ix = pos.0 & !CodeGenResultComments::CONTINUATION_BIT;
3106            let ModulePosition(module, pos) = lookup_table[ix as usize];
3107
3108            return (module as usize, BytePos(pos));
3109        }
3110
3111        let high_bits_set = pos.0 >> 30 & 1 == 1;
3112        let module = (pos.0 << 2) >> (pos_width + 2);
3113        let pos = pos.0 & !((2u32.pow(header_width) - 1) << pos_width);
3114        let pos = if high_bits_set {
3115            pos | ((2u32.pow(header_width) - 1) << pos_width)
3116        } else {
3117            pos
3118        };
3119        (module as usize, BytePos(pos))
3120    }
3121}
3122
3123enum CodeGenResultCommentsConsumable<'a> {
3124    Single {
3125        comments: CowComments<'a>,
3126        extra_comments: &'a SwcComments,
3127    },
3128    ScopeHoisting {
3129        modules_header_width: u32,
3130        lookup_table: Arc<Mutex<Vec<ModulePosition>>>,
3131        comments: Vec<CodeGenResultCommentsConsumable<'a>>,
3132    },
3133    Empty,
3134}
3135/// All BytePos in Spans in the AST are encoded correctly in [`merge_modules`], but the Comments
3136/// also contain spans. These also need to be encoded so that all pos in `mappings` are consistently
3137/// encoded.
3138fn encode_module_into_comment_span(
3139    modules_header_width: u32,
3140    module: usize,
3141    mut comment: Comment,
3142    lookup_table: Arc<Mutex<Vec<ModulePosition>>>,
3143) -> Comment {
3144    comment.span.lo = CodeGenResultComments::encode_bytepos(
3145        modules_header_width,
3146        module as u32,
3147        comment.span.lo,
3148        lookup_table.clone(),
3149    )
3150    .unwrap();
3151    comment.span.hi = CodeGenResultComments::encode_bytepos(
3152        modules_header_width,
3153        module as u32,
3154        comment.span.hi,
3155        lookup_table,
3156    )
3157    .unwrap();
3158    comment
3159}
3160
3161impl Comments for CodeGenResultCommentsConsumable<'_> {
3162    fn add_leading(&self, _pos: BytePos, _cmt: Comment) {
3163        unimplemented!("add_leading")
3164    }
3165
3166    fn add_leading_comments(&self, _pos: BytePos, _comments: Vec<Comment>) {
3167        unimplemented!("add_leading_comments")
3168    }
3169
3170    fn has_leading(&self, pos: BytePos) -> bool {
3171        if pos.is_dummy() {
3172            return false;
3173        }
3174        match self {
3175            Self::Single {
3176                comments,
3177                extra_comments,
3178            } => comments.has_leading(pos) || extra_comments.has_leading(pos),
3179            Self::ScopeHoisting {
3180                modules_header_width,
3181                lookup_table,
3182                comments,
3183            } => {
3184                let (module, pos) =
3185                    CodeGenResultComments::decode_bytepos(*modules_header_width, pos, lookup_table);
3186                comments[module].has_leading(pos)
3187            }
3188            Self::Empty => false,
3189        }
3190    }
3191
3192    fn move_leading(&self, _from: BytePos, _to: BytePos) {
3193        unimplemented!("move_leading")
3194    }
3195
3196    fn take_leading(&self, pos: BytePos) -> Option<Vec<Comment>> {
3197        if pos.is_dummy() {
3198            return None;
3199        }
3200        match self {
3201            Self::Single {
3202                comments,
3203                extra_comments,
3204            } => merge_option_vec(comments.take_leading(pos), extra_comments.take_leading(pos)),
3205            Self::ScopeHoisting {
3206                modules_header_width,
3207                lookup_table,
3208                comments,
3209            } => {
3210                let (module, pos) =
3211                    CodeGenResultComments::decode_bytepos(*modules_header_width, pos, lookup_table);
3212                comments[module].take_leading(pos).map(|comments| {
3213                    comments
3214                        .into_iter()
3215                        .map(|c| {
3216                            encode_module_into_comment_span(
3217                                *modules_header_width,
3218                                module,
3219                                c,
3220                                lookup_table.clone(),
3221                            )
3222                        })
3223                        .collect()
3224                })
3225            }
3226            Self::Empty => None,
3227        }
3228    }
3229
3230    fn get_leading(&self, pos: BytePos) -> Option<Vec<Comment>> {
3231        if pos.is_dummy() {
3232            return None;
3233        }
3234        match self {
3235            Self::Single {
3236                comments,
3237                extra_comments,
3238            } => merge_option_vec(comments.get_leading(pos), extra_comments.get_leading(pos)),
3239            Self::ScopeHoisting {
3240                modules_header_width,
3241                lookup_table,
3242                comments,
3243            } => {
3244                let (module, pos) =
3245                    CodeGenResultComments::decode_bytepos(*modules_header_width, pos, lookup_table);
3246                comments[module].get_leading(pos).map(|comments| {
3247                    comments
3248                        .into_iter()
3249                        .map(|c| {
3250                            encode_module_into_comment_span(
3251                                *modules_header_width,
3252                                module,
3253                                c,
3254                                lookup_table.clone(),
3255                            )
3256                        })
3257                        .collect()
3258                })
3259            }
3260            Self::Empty => None,
3261        }
3262    }
3263
3264    fn add_trailing(&self, _pos: BytePos, _cmt: Comment) {
3265        unimplemented!("add_trailing")
3266    }
3267
3268    fn add_trailing_comments(&self, _pos: BytePos, _comments: Vec<Comment>) {
3269        unimplemented!("add_trailing_comments")
3270    }
3271
3272    fn has_trailing(&self, pos: BytePos) -> bool {
3273        if pos.is_dummy() {
3274            return false;
3275        }
3276        match self {
3277            Self::Single {
3278                comments,
3279                extra_comments,
3280            } => comments.has_trailing(pos) || extra_comments.has_trailing(pos),
3281            Self::ScopeHoisting {
3282                modules_header_width,
3283                lookup_table,
3284                comments,
3285            } => {
3286                let (module, pos) =
3287                    CodeGenResultComments::decode_bytepos(*modules_header_width, pos, lookup_table);
3288                comments[module].has_trailing(pos)
3289            }
3290            Self::Empty => false,
3291        }
3292    }
3293
3294    fn move_trailing(&self, _from: BytePos, _to: BytePos) {
3295        unimplemented!("move_trailing")
3296    }
3297
3298    fn take_trailing(&self, pos: BytePos) -> Option<Vec<Comment>> {
3299        if pos.is_dummy() {
3300            return None;
3301        }
3302        match self {
3303            Self::Single {
3304                comments,
3305                extra_comments,
3306            } => merge_option_vec(
3307                comments.take_trailing(pos),
3308                extra_comments.take_trailing(pos),
3309            ),
3310            Self::ScopeHoisting {
3311                modules_header_width,
3312                lookup_table,
3313                comments,
3314            } => {
3315                let (module, pos) =
3316                    CodeGenResultComments::decode_bytepos(*modules_header_width, pos, lookup_table);
3317                comments[module].take_trailing(pos).map(|comments| {
3318                    comments
3319                        .into_iter()
3320                        .map(|c| {
3321                            encode_module_into_comment_span(
3322                                *modules_header_width,
3323                                module,
3324                                c,
3325                                lookup_table.clone(),
3326                            )
3327                        })
3328                        .collect()
3329                })
3330            }
3331            Self::Empty => None,
3332        }
3333    }
3334
3335    fn get_trailing(&self, pos: BytePos) -> Option<Vec<Comment>> {
3336        if pos.is_dummy() {
3337            return None;
3338        }
3339        match self {
3340            Self::Single {
3341                comments,
3342                extra_comments,
3343            } => merge_option_vec(comments.get_leading(pos), extra_comments.get_leading(pos)),
3344            Self::ScopeHoisting {
3345                modules_header_width,
3346                lookup_table,
3347                comments,
3348            } => {
3349                let (module, pos) =
3350                    CodeGenResultComments::decode_bytepos(*modules_header_width, pos, lookup_table);
3351                comments[module].get_leading(pos).map(|comments| {
3352                    comments
3353                        .into_iter()
3354                        .map(|c| {
3355                            encode_module_into_comment_span(
3356                                *modules_header_width,
3357                                module,
3358                                c,
3359                                lookup_table.clone(),
3360                            )
3361                        })
3362                        .collect()
3363                })
3364            }
3365            Self::Empty => None,
3366        }
3367    }
3368
3369    fn add_pure_comment(&self, _pos: BytePos) {
3370        unimplemented!("add_pure_comment")
3371    }
3372}
3373
3374fn merge_option_vec<T>(a: Option<Vec<T>>, b: Option<Vec<T>>) -> Option<Vec<T>> {
3375    match (a, b) {
3376        (Some(a), Some(b)) => Some(a.into_iter().chain(b).collect()),
3377        (Some(a), None) => Some(a),
3378        (None, Some(b)) => Some(b),
3379        (None, None) => None,
3380    }
3381}
3382
3383#[cfg(test)]
3384mod tests {
3385    use super::*;
3386    fn bytepos_ensure_identical(modules_header_width: u32, pos: BytePos) {
3387        let module_count = 2u32.pow(modules_header_width);
3388        let lookup_table = Arc::new(Mutex::new(Vec::new()));
3389
3390        for module in [
3391            0,
3392            1,
3393            2,
3394            module_count / 2,
3395            module_count.wrapping_sub(5),
3396            module_count.wrapping_sub(1),
3397        ]
3398        .into_iter()
3399        .filter(|&m| m < module_count)
3400        {
3401            let encoded = CodeGenResultComments::encode_bytepos(
3402                modules_header_width,
3403                module,
3404                pos,
3405                lookup_table.clone(),
3406            )
3407            .unwrap();
3408            let (decoded_module, decoded_pos) =
3409                CodeGenResultComments::decode_bytepos(modules_header_width, encoded, &lookup_table);
3410            assert_eq!(
3411                decoded_module as u32, module,
3412                "Testing width {modules_header_width} and pos {pos:?}"
3413            );
3414            assert_eq!(
3415                decoded_pos, pos,
3416                "Testing width {modules_header_width} and pos {pos:?}"
3417            );
3418        }
3419    }
3420
3421    #[test]
3422    fn test_encode_decode_bytepos_format() {
3423        let table = Arc::new(Mutex::new(Vec::new()));
3424
3425        for (pos, module, modules_header_width, result) in [
3426            (
3427                0b00000000000000000000000000000101,
3428                0b1,
3429                1,
3430                0b00100000000000000000000000000101,
3431            ),
3432            (
3433                0b00000000000000000000000000000101,
3434                0b01,
3435                2,
3436                0b00010000000000000000000000000101,
3437            ),
3438            (
3439                0b11111111111111110000000000000101,
3440                0b0110,
3441                4,
3442                0b01011011111111110000000000000101,
3443            ),
3444            (
3445                BytePos::PLACEHOLDER.0,
3446                0b01111,
3447                5,
3448                0b01011111111111111111111111111101,
3449            ),
3450            (
3451                BytePos::PURE.0,
3452                0b01111,
3453                5,
3454                0b01011111111111111111111111111110,
3455            ),
3456            (
3457                BytePos::SYNTHESIZED.0,
3458                0b01111,
3459                5,
3460                0b01011111111111111111111111111111,
3461            ),
3462            // This is an index that should trigger the overflow to store the position into the
3463            // lookup table
3464            (
3465                0b00000111111111110000000000000101,
3466                0b0001,
3467                4,
3468                0b10000000000000000000000000000000,
3469            ),
3470            // Another one should increase the index by 1
3471            (
3472                0b00000111111111110000000000111110,
3473                0b0001,
3474                4,
3475                0b10000000000000000000000000000001,
3476            ),
3477            // Special case, DUMMY stays a DUMMY
3478            (BytePos::DUMMY.0, 0b0001, 4, BytePos::DUMMY.0),
3479        ] {
3480            let encoded = CodeGenResultComments::encode_bytepos(
3481                modules_header_width,
3482                module,
3483                BytePos(pos),
3484                table.clone(),
3485            )
3486            .unwrap();
3487            assert_eq!(encoded.0, result);
3488
3489            // Ensure that the correct original module and bytepos are stored when overflow occurs
3490            if encoded.0 & CodeGenResultComments::CONTINUATION_BIT
3491                == CodeGenResultComments::CONTINUATION_BIT
3492            {
3493                let index = encoded.0 & !CodeGenResultComments::CONTINUATION_BIT;
3494                let ModulePosition(encoded_module, encoded_pos) =
3495                    table.lock().unwrap()[index as usize];
3496                assert_eq!(encoded_module, module);
3497                assert_eq!(encoded_pos, pos);
3498            }
3499        }
3500    }
3501
3502    #[test]
3503    fn test_encode_decode_bytepos_lossless() {
3504        // This is copied from swc (it's not exported), comments the range above this value.
3505        const DUMMY_RESERVE: u32 = u32::MAX - 2_u32.pow(16);
3506
3507        for modules_header_width in 1..=10 {
3508            for pos in [
3509                // BytePos::DUMMY, // This must never get decoded in the first place
3510                BytePos(1),
3511                BytePos(2),
3512                BytePos(100),
3513                BytePos(4_000_000),
3514                BytePos(600_000_000),
3515                BytePos(u32::MAX - 3), // The maximum allowed value that isn't reserved by SWC
3516                BytePos::PLACEHOLDER,
3517                BytePos::SYNTHESIZED,
3518                BytePos::PURE,
3519                BytePos(DUMMY_RESERVE),
3520                BytePos(DUMMY_RESERVE + 10),
3521                BytePos(DUMMY_RESERVE + 10000),
3522            ] {
3523                bytepos_ensure_identical(modules_header_width, pos);
3524            }
3525        }
3526    }
3527}