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