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 (path, collected) in &collection.modules {
49            if collected.is_external {
50                continue;
51            }
52
53            let module_id = convert_collected_module_id(path)
54                .map_err(|e| Error::InvalidConfig(format!("Module ID conversion failed: {}", e)))?;
55            path_to_id.insert(path.clone(), module_id);
56        }
57
58        // Second pass: convert modules
59        for (path, collected) in &collection.modules {
60            if collected.is_external {
61                continue;
62            }
63
64            let module_id = path_to_id.get(path).ok_or_else(|| {
65                Error::InvalidConfig(format!("Module ID not found for path: {}", path))
66            })?;
67
68            let exports = convert_collected_exports(collected, module_id);
69            let imports = convert_collected_imports(collected, module_id, &path_to_id);
70
71            let has_side_effects = collected.has_side_effects;
72
73            // Perform semantic analysis to extract symbols
74            let source_type = super::super::SourceType::from_path(module_id.as_path());
75            let code = collected.code.as_deref().unwrap_or("");
76            let mut symbol_table = analyze_symbols(
77                code,
78                module_id.as_path().to_str().unwrap_or("unknown"),
79                source_type,
80            )
81            .unwrap_or_default();
82
83            // Link exports to symbols - mark symbols as exported
84            let export_names: Vec<String> = exports.iter().map(|e| e.name.clone()).collect();
85            symbol_table.mark_exports(&export_names);
86
87            let mut builder = Module::builder(
88                module_id.clone(),
89                module_id.as_path().to_path_buf(),
90                source_type,
91            )
92            .exports(exports)
93            .side_effects(has_side_effects)
94            .original_size(code.len())
95            .bundled_size(None)
96            .external(false)
97            .symbol_table(symbol_table)
98            .module_format(FobModuleFormat::Unknown)
99            .exports_kind(infer_exports_kind(&collected.exports))
100            .has_star_exports(has_star_export(&collected.exports))
101            .execution_order(None);
102
103            if collected.is_entry {
104                builder = builder.entry(true);
105            }
106
107            let module = builder.build();
108            pending_modules.push((module_id.clone(), module, imports));
109        }
110
111        let mut external_aggregate: HashMap<String, ExternalDependency> = HashMap::default();
112
113        // Third pass: resolve imports and build graph
114        for (module_id, mut module, pending_imports) in pending_modules {
115            let mut resolved_imports = Vec::with_capacity(pending_imports.len());
116            for mut pending_import in pending_imports {
117                if let Some(target_path) = pending_import.target {
118                    if let Some(target_id) = path_to_id.get(&target_path) {
119                        pending_import.import.resolved_to = Some(target_id.clone());
120                        graph.add_dependency(module_id.clone(), target_id.clone())?;
121                    } else {
122                        // Any unresolved import is treated as external dependency
123                        let dep = external_aggregate
124                            .entry(target_path.clone())
125                            .or_insert_with(|| ExternalDependency::new(target_path.clone()));
126                        dep.push_importer(module_id.clone());
127                    }
128                }
129                resolved_imports.push(pending_import.import);
130            }
131
132            // Update module with resolved imports before adding
133            module.imports = std::sync::Arc::new(resolved_imports);
134            graph.add_module(module)?;
135        }
136
137        for dep in external_aggregate.into_values() {
138            graph.add_external_dependency(dep)?;
139        }
140
141        Ok(graph)
142    }
143}