fob_graph/memory/
construction.rs

1//! Construction methods for ModuleGraph.
2
3use rustc_hash::FxHashMap as HashMap;
4
5use super::super::external_dep::ExternalDependency;
6use super::super::{Module, ModuleId};
7use super::graph::{GraphInner, ModuleGraph};
8use crate::{Error, Result};
9
10impl ModuleGraph {
11    /// Create a new empty graph.
12    pub fn new() -> Result<Self> {
13        Ok(Self {
14            inner: std::sync::Arc::new(parking_lot::RwLock::new(GraphInner::default())),
15        })
16    }
17
18    /// Construct a graph from an iterator of modules (without edges).
19    pub fn from_modules<I>(modules: I) -> Result<Self>
20    where
21        I: IntoIterator<Item = Module>,
22    {
23        let graph = Self::new()?;
24        for module in modules {
25            graph.add_module(module)?;
26        }
27        Ok(graph)
28    }
29
30    /// Create a ModuleGraph from collected module data.
31    pub fn from_collected_data(
32        collection: super::super::collection::CollectionState,
33    ) -> Result<Self> {
34        use super::super::from_collection::{
35            PendingImport, convert_collected_exports, convert_collected_imports,
36            convert_collected_module_id, has_star_export, infer_exports_kind,
37        };
38        use super::super::module::ModuleFormat as FobModuleFormat;
39        use super::super::semantic::analyze_symbols;
40
41        let graph = Self::new()?;
42
43        let mut path_to_id: std::collections::HashMap<String, ModuleId> =
44            std::collections::HashMap::new();
45        let mut pending_modules: Vec<(ModuleId, Module, Vec<PendingImport>)> = Vec::new();
46
47        // First pass: create module IDs and identify externals
48        for entry in collection.modules.iter() {
49            let path = entry.key();
50            let collected = entry.value();
51            if collected.is_external {
52                continue;
53            }
54
55            let module_id = convert_collected_module_id(path)
56                .map_err(|e| Error::InvalidConfig(format!("Module ID conversion failed: {}", e)))?;
57            path_to_id.insert(path.clone(), module_id);
58        }
59
60        // Second pass: convert modules
61        for entry in collection.modules.iter() {
62            let path = entry.key();
63            let collected = entry.value();
64            if collected.is_external {
65                continue;
66            }
67
68            let module_id = path_to_id.get(path).ok_or_else(|| {
69                Error::InvalidConfig(format!("Module ID not found for path: {}", path))
70            })?;
71
72            let exports = convert_collected_exports(collected, module_id);
73            let imports = convert_collected_imports(collected, module_id, &path_to_id);
74
75            let has_side_effects = collected.has_side_effects;
76
77            // Perform semantic analysis to extract symbols
78            let source_type = super::super::SourceType::from_path(module_id.as_path());
79            let code = collected.code.as_deref().unwrap_or("");
80            let mut symbol_table = analyze_symbols(
81                code,
82                module_id.as_path().to_str().unwrap_or("unknown"),
83                source_type,
84            )
85            .unwrap_or_default();
86
87            // Link exports to symbols - mark symbols as exported
88            let export_names: Vec<String> = exports.iter().map(|e| e.name.clone()).collect();
89            symbol_table.mark_exports(&export_names);
90
91            let mut builder = Module::builder(
92                module_id.clone(),
93                module_id.as_path().to_path_buf(),
94                source_type,
95            )
96            .exports(exports)
97            .side_effects(has_side_effects)
98            .original_size(code.len())
99            .bundled_size(None)
100            .external(false)
101            .symbol_table(symbol_table)
102            .module_format(FobModuleFormat::Unknown)
103            .exports_kind(infer_exports_kind(&collected.exports))
104            .has_star_exports(has_star_export(&collected.exports))
105            .execution_order(None);
106
107            if collected.is_entry {
108                builder = builder.entry(true);
109            }
110
111            let module = builder.build();
112            pending_modules.push((module_id.clone(), module, imports));
113        }
114
115        let mut external_aggregate: HashMap<String, ExternalDependency> = HashMap::default();
116
117        // Third pass: resolve imports and build graph
118        for (module_id, mut module, pending_imports) in pending_modules {
119            let mut resolved_imports = Vec::with_capacity(pending_imports.len());
120            for mut pending_import in pending_imports {
121                if let Some(target_path) = pending_import.target {
122                    if let Some(target_id) = path_to_id.get(&target_path) {
123                        pending_import.import.resolved_to = Some(target_id.clone());
124                        graph.add_dependency(module_id.clone(), target_id.clone())?;
125                    } else {
126                        // Any unresolved import is treated as external dependency
127                        let dep = external_aggregate
128                            .entry(target_path.clone())
129                            .or_insert_with(|| ExternalDependency::new(target_path.clone()));
130                        dep.push_importer(module_id.clone());
131                    }
132                }
133                resolved_imports.push(pending_import.import);
134            }
135
136            // Update module with resolved imports before adding
137            module.imports = std::sync::Arc::new(resolved_imports);
138            graph.add_module(module)?;
139        }
140
141        for dep in external_aggregate.into_values() {
142            graph.add_external_dependency(dep)?;
143        }
144
145        Ok(graph)
146    }
147}