Skip to main content

turbopack_ecmascript/
lib.rs

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