agentic_codebase/semantic/
analyzer.rs1use std::collections::HashMap;
8
9use crate::graph::CodeGraph;
10use crate::parse::{RawCodeUnit, ReferenceKind};
11use crate::types::{
12 AcbResult, CodeUnit, CodeUnitType, Edge, EdgeType, Language, DEFAULT_DIMENSION,
13};
14
15use super::concept_extractor::{ConceptExtractor, ExtractedConcept};
16use super::ffi_tracer::{FfiEdge, FfiTracer};
17use super::pattern_detector::{PatternDetector, PatternInstance};
18use super::resolver::{Resolution, ResolvedUnit, Resolver, SymbolTable};
19
20#[derive(Debug, Clone)]
22pub struct AnalyzeOptions {
23 pub detect_patterns: bool,
25 pub extract_concepts: bool,
27 pub trace_ffi: bool,
29}
30
31impl Default for AnalyzeOptions {
32 fn default() -> Self {
33 Self {
34 detect_patterns: true,
35 extract_concepts: true,
36 trace_ffi: true,
37 }
38 }
39}
40
41pub struct SemanticAnalyzer {
43 resolver: Resolver,
44 ffi_tracer: FfiTracer,
45 pattern_detector: PatternDetector,
46 concept_extractor: ConceptExtractor,
47}
48
49impl SemanticAnalyzer {
50 pub fn new() -> Self {
52 Self {
53 resolver: Resolver::new(),
54 ffi_tracer: FfiTracer::new(),
55 pattern_detector: PatternDetector::new(),
56 concept_extractor: ConceptExtractor::new(),
57 }
58 }
59
60 pub fn analyze(
62 &self,
63 raw_units: Vec<RawCodeUnit>,
64 options: &AnalyzeOptions,
65 ) -> AcbResult<CodeGraph> {
66 let symbol_table = SymbolTable::build(&raw_units)?;
68
69 let resolved = self.resolver.resolve_all(&raw_units, &symbol_table)?;
71
72 let ffi_edges = if options.trace_ffi {
74 self.ffi_tracer.trace(&resolved)?
75 } else {
76 Vec::new()
77 };
78
79 let patterns = if options.detect_patterns {
81 self.pattern_detector.detect(&resolved)?
82 } else {
83 Vec::new()
84 };
85
86 let concepts = if options.extract_concepts {
88 self.concept_extractor.extract(&resolved)?
89 } else {
90 Vec::new()
91 };
92
93 self.build_graph(resolved, ffi_edges, patterns, concepts)
95 }
96
97 fn build_graph(
99 &self,
100 resolved: Vec<ResolvedUnit>,
101 ffi_edges: Vec<FfiEdge>,
102 patterns: Vec<PatternInstance>,
103 _concepts: Vec<ExtractedConcept>,
104 ) -> AcbResult<CodeGraph> {
105 let mut graph = CodeGraph::new(DEFAULT_DIMENSION);
106
107 let mut id_map: HashMap<u64, u64> = HashMap::new();
109
110 for runit in &resolved {
112 let raw = &runit.unit;
113 let code_unit = CodeUnit::new(
114 raw.unit_type,
115 raw.language,
116 raw.name.clone(),
117 raw.qualified_name.clone(),
118 raw.file_path.clone(),
119 raw.span,
120 );
121 let mut cu = code_unit;
122 cu.signature = raw.signature.clone();
123 cu.doc_summary = raw.doc.clone();
124 cu.visibility = raw.visibility;
125 cu.complexity = raw.complexity;
126 cu.is_async = raw.is_async;
127 cu.is_generator = raw.is_generator;
128
129 let graph_id = graph.add_unit(cu);
130 id_map.insert(raw.temp_id, graph_id);
131 }
132
133 for runit in &resolved {
135 let source_temp = runit.unit.temp_id;
136 let source_graph_id = match id_map.get(&source_temp) {
137 Some(&id) => id,
138 None => continue,
139 };
140
141 for ref_info in &runit.resolved_refs {
142 match &ref_info.resolution {
143 Resolution::Local(target_temp) => {
144 if let Some(&target_graph_id) = id_map.get(target_temp) {
145 if source_graph_id == target_graph_id {
146 continue; }
148 let edge_type = reference_to_edge_type(ref_info.raw.kind);
149 let edge = Edge::new(source_graph_id, target_graph_id, edge_type);
150 let _ = graph.add_edge(edge);
152 }
153 }
154 Resolution::Imported(imported) => {
155 if let Some(&target_graph_id) = id_map.get(&imported.unit_id) {
156 if source_graph_id == target_graph_id {
157 continue;
158 }
159 let edge =
160 Edge::new(source_graph_id, target_graph_id, EdgeType::Imports);
161 let _ = graph.add_edge(edge);
162 }
163 }
164 Resolution::External(_) | Resolution::Unresolved => {
165 }
167 }
168 }
169 }
170
171 self.add_containment_edges(&resolved, &id_map, &mut graph);
173
174 for ffi_edge in ffi_edges {
176 if let Some(&source_id) = id_map.get(&ffi_edge.source_id) {
177 if let Some(target_temp) = ffi_edge.target_id {
178 if let Some(&target_id) = id_map.get(&target_temp) {
179 if source_id != target_id {
180 let edge = Edge::new(source_id, target_id, EdgeType::FfiBinds);
181 let _ = graph.add_edge(edge);
182 }
183 }
184 }
185 }
186 }
187
188 for pattern in patterns {
190 if let Some(&primary_id) = id_map.get(&pattern.primary_unit) {
191 let pattern_unit = CodeUnit::new(
193 CodeUnitType::Pattern,
194 Language::Unknown,
195 pattern.pattern_name.clone(),
196 format!("pattern::{}", pattern.pattern_name),
197 std::path::PathBuf::new(),
198 crate::types::Span::point(0, 0),
199 );
200 let pattern_graph_id = graph.add_unit(pattern_unit);
201 let edge = Edge::new(primary_id, pattern_graph_id, EdgeType::PatternOf);
202 let _ = graph.add_edge(edge);
203 }
204 }
205
206 Ok(graph)
207 }
208
209 fn add_containment_edges(
211 &self,
212 resolved: &[ResolvedUnit],
213 id_map: &HashMap<u64, u64>,
214 graph: &mut CodeGraph,
215 ) {
216 let mut file_groups: HashMap<String, Vec<&ResolvedUnit>> = HashMap::new();
218 for runit in resolved {
219 let key = runit.unit.file_path.to_string_lossy().to_string();
220 file_groups.entry(key).or_default().push(runit);
221 }
222
223 for units_in_file in file_groups.values() {
224 let module = units_in_file
226 .iter()
227 .find(|u| u.unit.unit_type == CodeUnitType::Module);
228
229 if let Some(module_unit) = module {
230 let module_graph_id = match id_map.get(&module_unit.unit.temp_id) {
231 Some(&id) => id,
232 None => continue,
233 };
234
235 for other in units_in_file {
237 if other.unit.temp_id == module_unit.unit.temp_id {
238 continue;
239 }
240 if other.unit.unit_type == CodeUnitType::Import {
241 continue;
242 }
243 if let Some(&other_id) = id_map.get(&other.unit.temp_id) {
244 let edge = Edge::new(module_graph_id, other_id, EdgeType::Contains);
245 let _ = graph.add_edge(edge);
246 }
247 }
248 }
249 }
250 }
251}
252
253impl Default for SemanticAnalyzer {
254 fn default() -> Self {
255 Self::new()
256 }
257}
258
259fn reference_to_edge_type(kind: ReferenceKind) -> EdgeType {
261 match kind {
262 ReferenceKind::Call => EdgeType::Calls,
263 ReferenceKind::Import => EdgeType::Imports,
264 ReferenceKind::TypeUse => EdgeType::UsesType,
265 ReferenceKind::Inherit => EdgeType::Inherits,
266 ReferenceKind::Implement => EdgeType::Implements,
267 ReferenceKind::Access => EdgeType::References,
268 }
269}