Skip to main content

turbopack_ecmascript/analyzer/graph/
mod.rs

1use rustc_hash::FxHashMap;
2use swc_core::{
3    atoms::Atom,
4    common::BytePos,
5    ecma::{ast::*, visit::VisitWithAstPath},
6};
7use turbopack_core::resolve::ExportUsage;
8
9pub use crate::analyzer::graph::{
10    effects::{
11        AssignmentScope, AssignmentScopes, ConditionalKind, Effect, EffectArg, EffectsBlock,
12    },
13    eval_context::EvalContext,
14};
15use crate::{
16    AnalyzeMode, SpecifiedModuleType,
17    analyzer::{Bump, JsValue, graph::visitor::Analyzer},
18    ast_path_trie::AstPathTrieBuilder,
19    chunk::CjsStaticExports,
20    code_gen::CodeGen,
21};
22
23mod effects;
24mod eval_context;
25pub(crate) mod visitor;
26
27#[derive(Debug)]
28pub struct VarGraph<'a> {
29    pub values: FxHashMap<Id, JsValue<'a>>,
30
31    /// Map [`JsValue::FreeVar`] names to their [`Id`] to facilitate lookups into [`Self::values`].
32    ///
33    /// Doesn't necessarily contain every [`FreeVar`][JsValue::FreeVar], just those who have
34    /// non-trivial values.
35    pub free_var_ids: FxHashMap<Atom, Id>,
36
37    pub effects: Vec<Effect<'a>>,
38    // Some unconditional codegens, usually for ESM items.
39    pub code_gens: Vec<CodeGen>,
40    /// Interns the AST paths used by `code_gens`. Effect processing keeps interning into
41    /// this same trie, so every path for the module ends up sharing one arena.
42    pub ast_paths: AstPathTrieBuilder,
43
44    /// [`ExportUsage`] per `require("…")` call, keyed by call position; absent
45    /// calls fall back to `ExportUsage::All`.
46    pub require_usage: FxHashMap<BytePos, ExportUsage>,
47
48    /// Present when the module is a statically-analyzable CommonJS module (no
49    /// dynamic exports); carries its named exports for scope hoisting.
50    pub cjs_static_exports: Option<CjsStaticExports>,
51
52    /// `true` when the module contains a direct `eval(...)` call (a call to the unshadowed
53    /// global `eval`). A direct eval can access bindings in its lexical scope by name, so the
54    /// module must not be merged with others (merging may rename those bindings).
55    pub has_direct_eval: bool,
56}
57
58impl<'a> VarGraph<'a> {
59    pub fn normalize(&mut self, arena: &'a Bump) {
60        for value in self.values.values_mut() {
61            value.normalize(arena);
62        }
63        for effect in self.effects.iter_mut() {
64            effect.normalize(arena);
65        }
66    }
67}
68
69pub fn create_graph<'a>(
70    arena: &'a Bump,
71    m: &Program,
72    eval_context: &EvalContext,
73    analyze_mode: AnalyzeMode,
74    supports_block_scoping: bool,
75    specified_module_type: SpecifiedModuleType,
76    cjs_tree_shaking: bool,
77    cjs_scope_hoisting: bool,
78) -> VarGraph<'a> {
79    let mut analyzer = Analyzer {
80        arena,
81        analyze_mode,
82        data: VarGraph {
83            values: Default::default(),
84            free_var_ids: Default::default(),
85            effects: Default::default(),
86            code_gens: Default::default(),
87            ast_paths: Default::default(),
88            require_usage: Default::default(),
89            cjs_static_exports: Default::default(),
90            has_direct_eval: false,
91        },
92        eval_context,
93        state: Default::default(),
94        effects: Default::default(),
95        hoisted_effects: Default::default(),
96        code_gens: Default::default(),
97        ast_paths: Default::default(),
98        supports_block_scoping,
99    };
100
101    // CommonJS export recognition runs for a CommonJS module that emits code when either CJS
102    // tree-shaking or CJS scope hoisting is enabled (both consume the static export analysis).
103    if (cjs_tree_shaking || cjs_scope_hoisting)
104        && analyze_mode.is_codegen
105        && eval_context.is_cjs(specified_module_type)
106    {
107        analyzer.enable_cjs_exports();
108    }
109
110    if cjs_tree_shaking && analyze_mode.is_codegen {
111        analyzer.enable_require_usage(&eval_context.imports);
112    }
113
114    m.visit_with_ast_path(&mut analyzer, &mut Default::default());
115
116    let mut graph = analyzer.data;
117    graph.normalize(arena);
118
119    graph
120}