Skip to main content

aster/map/server/services/
dependency.rs

1//! 依赖分析服务
2//!
3//! 负责入口点检测和依赖树构建
4
5use regex::Regex;
6use std::collections::{HashMap, HashSet};
7
8use crate::map::server::types::DependencyTreeNode;
9use crate::map::types_enhanced::{EnhancedCodeBlueprint, ModuleDependency};
10
11/// 入口文件名模式
12static ENTRY_PATTERNS: &[&str] = &[
13    r"cli\.(ts|js)$",
14    r"index\.(ts|js)$",
15    r"main\.(ts|js)$",
16    r"app\.(ts|js)$",
17    r"server\.(ts|js)$",
18    r"entry\.(ts|js)$",
19];
20
21/// 检测项目入口点
22pub fn detect_entry_points(blueprint: &EnhancedCodeBlueprint) -> Vec<String> {
23    let entry_patterns: Vec<Regex> = ENTRY_PATTERNS
24        .iter()
25        .filter_map(|p| Regex::new(p).ok())
26        .collect();
27
28    // 计算每个模块被导入的次数
29    let mut import_counts: HashMap<String, usize> = HashMap::new();
30    for dep in &blueprint.references.module_deps {
31        *import_counts.entry(dep.target.clone()).or_insert(0) += 1;
32    }
33
34    let mut candidates: Vec<(String, i32)> = Vec::new();
35
36    for module in blueprint.modules.values() {
37        use once_cell::sync::Lazy;
38        static ROOT_PATTERN: Lazy<Regex> =
39            Lazy::new(|| Regex::new(r"^(src/)?[^/]+\.(ts|js)$").unwrap());
40
41        let mut score: i32 = 0;
42
43        // 入口文件名模式匹配
44        for (i, pattern) in entry_patterns.iter().enumerate() {
45            if pattern.is_match(&module.id) {
46                score += ((entry_patterns.len() - i) * 10) as i32;
47                break;
48            }
49        }
50
51        // 在根目录或 src 目录下的文件加分
52        if ROOT_PATTERN.is_match(&module.id) {
53            score += 5;
54        }
55
56        // 不被任何其他模块导入的文件加分
57        let import_count = import_counts.get(&module.id).copied().unwrap_or(0);
58        if import_count == 0 {
59            score += 20;
60        }
61
62        // 有导入其他模块的文件加分
63        if !module.imports.is_empty() {
64            score += module.imports.len().min(10) as i32;
65        }
66
67        if score > 0 {
68            candidates.push((module.id.clone(), score));
69        }
70    }
71
72    candidates.sort_by(|a, b| b.1.cmp(&a.1));
73    candidates.into_iter().take(5).map(|(id, _)| id).collect()
74}
75
76/// 构建从入口点开始的依赖树
77pub fn build_dependency_tree(
78    blueprint: &EnhancedCodeBlueprint,
79    entry_id: &str,
80    max_depth: usize,
81) -> Option<DependencyTreeNode> {
82    let _module = blueprint.modules.get(entry_id)?;
83
84    // 构建依赖图
85    let mut deps_by_source: HashMap<String, Vec<&ModuleDependency>> = HashMap::new();
86    for dep in &blueprint.references.module_deps {
87        deps_by_source
88            .entry(dep.source.clone())
89            .or_default()
90            .push(dep);
91    }
92
93    fn build_node(
94        blueprint: &EnhancedCodeBlueprint,
95        deps_by_source: &HashMap<String, Vec<&ModuleDependency>>,
96        module_id: &str,
97        depth: usize,
98        max_depth: usize,
99        visited: &mut HashSet<String>,
100    ) -> Option<DependencyTreeNode> {
101        let module = blueprint.modules.get(module_id)?;
102        let is_circular = visited.contains(module_id);
103
104        let mut node = DependencyTreeNode {
105            id: module_id.to_string(),
106            name: module.name.clone(),
107            path: module.path.clone(),
108            language: Some(module.language.clone()),
109            lines: Some(module.lines),
110            semantic: module
111                .semantic
112                .as_ref()
113                .map(|s| serde_json::to_value(s).unwrap_or_default()),
114            children: Vec::new(),
115            depth,
116            is_circular: if is_circular { Some(true) } else { None },
117        };
118
119        if is_circular || depth >= max_depth {
120            return Some(node);
121        }
122
123        visited.insert(module_id.to_string());
124
125        if let Some(deps) = deps_by_source.get(module_id) {
126            let mut sorted_deps: Vec<_> = deps.iter().collect();
127            sorted_deps.sort_by(|a, b| a.target.cmp(&b.target));
128
129            for dep in sorted_deps {
130                if blueprint.modules.contains_key(&dep.target) {
131                    if let Some(child) = build_node(
132                        blueprint,
133                        deps_by_source,
134                        &dep.target,
135                        depth + 1,
136                        max_depth,
137                        visited,
138                    ) {
139                        node.children.push(child);
140                    }
141                }
142            }
143        }
144
145        visited.remove(module_id);
146        Some(node)
147    }
148
149    let mut visited = HashSet::new();
150    build_node(
151        blueprint,
152        &deps_by_source,
153        entry_id,
154        0,
155        max_depth,
156        &mut visited,
157    )
158}