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