ricecoder_research/
dependency_analyzer.rs

1//! Dependency analysis for multiple programming languages
2//!
3//! Supports parsing dependencies from manifest files for 11+ languages:
4//! - Rust (Cargo.toml)
5//! - Node.js (package.json)
6//! - Python (pyproject.toml, requirements.txt)
7//! - Go (go.mod)
8//! - Java (pom.xml, build.gradle)
9//! - Kotlin (build.gradle.kts, pom.xml)
10//! - .NET (.csproj, packages.config)
11//! - PHP (composer.json)
12//! - Ruby (Gemfile)
13//! - Swift (Package.swift)
14//! - Dart/Flutter (pubspec.yaml)
15
16use crate::error::ResearchError;
17use crate::models::{Dependency, Language};
18use std::path::Path;
19use tracing::debug;
20
21mod dart_parser;
22mod dotnet_parser;
23mod go_parser;
24mod java_parser;
25mod kotlin_parser;
26mod nodejs_parser;
27mod php_parser;
28mod python_parser;
29mod ruby_parser;
30mod rust_parser;
31mod swift_parser;
32mod version_analyzer;
33
34pub use dart_parser::DartParser;
35pub use dotnet_parser::DotNetParser;
36pub use go_parser::GoParser;
37pub use java_parser::JavaParser;
38pub use kotlin_parser::KotlinParser;
39pub use nodejs_parser::NodeJsParser;
40pub use php_parser::PhpParser;
41pub use python_parser::PythonParser;
42pub use ruby_parser::RubyParser;
43pub use rust_parser::RustParser;
44pub use swift_parser::SwiftParser;
45pub use version_analyzer::VersionAnalyzer;
46
47/// Analyzes project dependencies across multiple languages
48#[derive(Debug)]
49pub struct DependencyAnalyzer {
50    rust_parser: RustParser,
51    nodejs_parser: NodeJsParser,
52    python_parser: PythonParser,
53    go_parser: GoParser,
54    java_parser: JavaParser,
55    kotlin_parser: KotlinParser,
56    dotnet_parser: DotNetParser,
57    php_parser: PhpParser,
58    ruby_parser: RubyParser,
59    swift_parser: SwiftParser,
60    dart_parser: DartParser,
61    version_analyzer: VersionAnalyzer,
62}
63
64impl DependencyAnalyzer {
65    /// Creates a new DependencyAnalyzer
66    pub fn new() -> Self {
67        DependencyAnalyzer {
68            rust_parser: RustParser::new(),
69            nodejs_parser: NodeJsParser::new(),
70            python_parser: PythonParser::new(),
71            go_parser: GoParser::new(),
72            java_parser: JavaParser::new(),
73            kotlin_parser: KotlinParser::new(),
74            dotnet_parser: DotNetParser::new(),
75            php_parser: PhpParser::new(),
76            ruby_parser: RubyParser::new(),
77            swift_parser: SwiftParser::new(),
78            dart_parser: DartParser::new(),
79            version_analyzer: VersionAnalyzer::new(),
80        }
81    }
82
83    /// Analyzes dependencies in a project
84    ///
85    /// Detects project language(s) and routes to appropriate parser(s).
86    /// Returns all dependencies found across all detected languages.
87    pub fn analyze(&self, root: &Path) -> Result<Vec<Dependency>, ResearchError> {
88        debug!("Analyzing dependencies in {:?}", root);
89
90        let mut all_dependencies = Vec::new();
91
92        // Try each language parser
93        if let Ok(deps) = self.rust_parser.parse(root) {
94            debug!("Found {} Rust dependencies", deps.len());
95            all_dependencies.extend(deps);
96        }
97
98        if let Ok(deps) = self.nodejs_parser.parse(root) {
99            debug!("Found {} Node.js dependencies", deps.len());
100            all_dependencies.extend(deps);
101        }
102
103        if let Ok(deps) = self.python_parser.parse(root) {
104            debug!("Found {} Python dependencies", deps.len());
105            all_dependencies.extend(deps);
106        }
107
108        if let Ok(deps) = self.go_parser.parse(root) {
109            debug!("Found {} Go dependencies", deps.len());
110            all_dependencies.extend(deps);
111        }
112
113        if let Ok(deps) = self.java_parser.parse(root) {
114            debug!("Found {} Java dependencies", deps.len());
115            all_dependencies.extend(deps);
116        }
117
118        if let Ok(deps) = self.kotlin_parser.parse(root) {
119            debug!("Found {} Kotlin dependencies", deps.len());
120            all_dependencies.extend(deps);
121        }
122
123        if let Ok(deps) = self.dotnet_parser.parse(root) {
124            debug!("Found {} .NET dependencies", deps.len());
125            all_dependencies.extend(deps);
126        }
127
128        if let Ok(deps) = self.php_parser.parse(root) {
129            debug!("Found {} PHP dependencies", deps.len());
130            all_dependencies.extend(deps);
131        }
132
133        if let Ok(deps) = self.ruby_parser.parse(root) {
134            debug!("Found {} Ruby dependencies", deps.len());
135            all_dependencies.extend(deps);
136        }
137
138        if let Ok(deps) = self.swift_parser.parse(root) {
139            debug!("Found {} Swift dependencies", deps.len());
140            all_dependencies.extend(deps);
141        }
142
143        if let Ok(deps) = self.dart_parser.parse(root) {
144            debug!("Found {} Dart/Flutter dependencies", deps.len());
145            all_dependencies.extend(deps);
146        }
147
148        // Remove duplicates (same name and version)
149        all_dependencies
150            .sort_by(|a, b| a.name.cmp(&b.name).then_with(|| a.version.cmp(&b.version)));
151        all_dependencies.dedup_by(|a, b| a.name == b.name && a.version == b.version);
152
153        Ok(all_dependencies)
154    }
155
156    /// Detects which languages are present in a project
157    pub fn detect_languages(&self, root: &Path) -> Result<Vec<Language>, ResearchError> {
158        let mut languages = Vec::new();
159
160        if self.rust_parser.has_manifest(root) {
161            languages.push(Language::Rust);
162        }
163
164        if self.nodejs_parser.has_manifest(root) {
165            languages.push(Language::TypeScript);
166        }
167
168        if self.python_parser.has_manifest(root) {
169            languages.push(Language::Python);
170        }
171
172        if self.go_parser.has_manifest(root) {
173            languages.push(Language::Go);
174        }
175
176        if self.java_parser.has_manifest(root) {
177            languages.push(Language::Java);
178        }
179
180        if self.kotlin_parser.has_manifest(root) {
181            languages.push(Language::Kotlin);
182        }
183
184        if self.dotnet_parser.has_manifest(root) {
185            languages.push(Language::CSharp);
186        }
187
188        if self.php_parser.has_manifest(root) {
189            languages.push(Language::Php);
190        }
191
192        if self.ruby_parser.has_manifest(root) {
193            languages.push(Language::Ruby);
194        }
195
196        if self.swift_parser.has_manifest(root) {
197            languages.push(Language::Swift);
198        }
199
200        if self.dart_parser.has_manifest(root) {
201            languages.push(Language::Dart);
202        }
203
204        Ok(languages)
205    }
206
207    /// Analyzes version conflicts across dependencies
208    pub fn analyze_version_conflicts(&self, dependencies: &[Dependency]) -> Vec<VersionConflict> {
209        self.version_analyzer.find_conflicts(dependencies)
210    }
211
212    /// Suggests version updates for dependencies
213    pub fn suggest_updates(&self, dependencies: &[Dependency]) -> Vec<VersionUpdate> {
214        self.version_analyzer.suggest_updates(dependencies)
215    }
216}
217
218impl Default for DependencyAnalyzer {
219    fn default() -> Self {
220        Self::new()
221    }
222}
223
224/// Represents a version conflict between dependencies
225#[derive(Debug, Clone)]
226pub struct VersionConflict {
227    /// Name of the dependency
228    pub dependency_name: String,
229    /// Conflicting versions
230    pub versions: Vec<String>,
231    /// Description of the conflict
232    pub description: String,
233}
234
235/// Represents a suggested version update
236#[derive(Debug, Clone)]
237pub struct VersionUpdate {
238    /// Name of the dependency
239    pub dependency_name: String,
240    /// Current version
241    pub current_version: String,
242    /// Suggested version
243    pub suggested_version: String,
244    /// Reason for the update
245    pub reason: String,
246}
247
248/// Trait for language-specific dependency parsers
249pub trait DependencyParser: Send + Sync {
250    /// Parses dependencies from manifest files
251    fn parse(&self, root: &Path) -> Result<Vec<Dependency>, ResearchError>;
252
253    /// Checks if the language's manifest file exists
254    fn has_manifest(&self, root: &Path) -> bool;
255}
256
257#[cfg(test)]
258mod tests {
259    use super::*;
260
261    #[test]
262    fn test_dependency_analyzer_creation() {
263        let analyzer = DependencyAnalyzer::new();
264        // Verify analyzer is created successfully
265        assert!(true);
266    }
267
268    #[test]
269    fn test_dependency_analyzer_default() {
270        let analyzer = DependencyAnalyzer::default();
271        // Verify default creation works
272        assert!(true);
273    }
274}