Skip to main content

turbopack_ecmascript/
lib.rs

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