syncable_cli/analyzer/
mod.rs

1//! # Analyzer Module
2//! 
3//! This module provides project analysis capabilities for detecting:
4//! - Programming languages and their versions
5//! - Frameworks and libraries
6//! - Dependencies and their versions
7//! - Entry points and exposed ports
8
9use crate::error::Result;
10use chrono::Utc;
11use serde::{Deserialize, Serialize};
12use std::collections::HashMap;
13use std::path::{Path, PathBuf};
14
15pub mod dependency_parser;
16pub mod framework_detector;
17pub mod language_detector;
18pub mod project_context;
19pub mod vulnerability_checker;
20pub mod security_analyzer;
21pub mod tool_installer;
22
23// Re-export dependency analysis types
24pub use dependency_parser::{
25    DependencyInfo, DependencyAnalysis, DetailedDependencyMap,
26    Vulnerability, VulnerabilitySeverity
27};
28
29// Re-export security analysis types
30pub use security_analyzer::{
31    SecurityAnalyzer, SecurityReport, SecurityFinding, SecuritySeverity,
32    SecurityCategory, ComplianceStatus, SecurityAnalysisConfig
33};
34
35/// Represents a detected programming language
36#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
37pub struct DetectedLanguage {
38    pub name: String,
39    pub version: Option<String>,
40    pub confidence: f32,
41    pub files: Vec<PathBuf>,
42    pub main_dependencies: Vec<String>,
43    pub dev_dependencies: Vec<String>,
44    pub package_manager: Option<String>,
45}
46
47/// Categories of detected technologies with proper classification
48#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
49pub enum TechnologyCategory {
50    /// Full-stack meta-frameworks that provide complete application structure
51    MetaFramework,
52    /// Frontend frameworks that provide application structure (Angular, Svelte)
53    FrontendFramework,
54    /// Backend frameworks that provide server structure (Express, Django, Spring Boot)
55    BackendFramework,
56    /// Libraries that provide specific functionality (React, Tanstack Query, Axios)
57    Library(LibraryType),
58    /// Build and development tools (Vite, Webpack, Rollup)
59    BuildTool,
60    /// Database and ORM tools (Prisma, TypeORM, SQLAlchemy)
61    Database,
62    /// Testing frameworks and libraries (Jest, Vitest, Cypress)
63    Testing,
64    /// JavaScript/Python/etc runtimes (Node.js, Bun, Deno)
65    Runtime,
66    /// Package managers (npm, yarn, pnpm, pip, cargo)
67    PackageManager,
68}
69
70/// Specific types of libraries for better classification
71#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
72pub enum LibraryType {
73    /// UI libraries (React, Vue, Preact)
74    UI,
75    /// State management (Zustand, Redux, Pinia)
76    StateManagement,
77    /// Data fetching (Tanstack Query, Apollo, Relay)
78    DataFetching,
79    /// Routing (React Router, Vue Router - when not meta-framework)
80    Routing,
81    /// Styling (Styled Components, Emotion, Tailwind)
82    Styling,
83    /// Utilities (Lodash, Date-fns, Zod)
84    Utility,
85    /// HTTP clients (Axios, Fetch libraries)
86    HttpClient,
87    /// Authentication (Auth0, Firebase Auth)
88    Authentication,
89    /// Other specific types
90    Other(String),
91}
92
93/// Represents a detected technology (framework, library, or tool)
94#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
95pub struct DetectedTechnology {
96    pub name: String,
97    pub version: Option<String>,
98    pub category: TechnologyCategory,
99    pub confidence: f32,
100    /// Dependencies this technology requires (e.g., Next.js requires React)
101    pub requires: Vec<String>,
102    /// Technologies that conflict with this one (e.g., Tanstack Start conflicts with React Router v7)
103    pub conflicts_with: Vec<String>,
104    /// Whether this is the primary technology driving the architecture
105    pub is_primary: bool,
106}
107
108/// Represents a service within a microservice architecture
109#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
110pub struct ServiceAnalysis {
111    pub name: String,
112    pub path: PathBuf,
113    pub languages: Vec<DetectedLanguage>,
114    pub technologies: Vec<DetectedTechnology>,
115    pub entry_points: Vec<EntryPoint>,
116    pub ports: Vec<Port>,
117    pub environment_variables: Vec<EnvVar>,
118    pub build_scripts: Vec<BuildScript>,
119    pub service_type: ProjectType,
120}
121
122/// Represents application entry points
123#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
124pub struct EntryPoint {
125    pub file: PathBuf,
126    pub function: Option<String>,
127    pub command: Option<String>,
128}
129
130/// Represents exposed network ports
131#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
132pub struct Port {
133    pub number: u16,
134    pub protocol: Protocol,
135    pub description: Option<String>,
136}
137
138#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
139pub enum Protocol {
140    Tcp,
141    Udp,
142    Http,
143    Https,
144}
145
146/// Represents environment variables
147#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
148pub struct EnvVar {
149    pub name: String,
150    pub default_value: Option<String>,
151    pub required: bool,
152    pub description: Option<String>,
153}
154
155/// Represents different project types
156#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
157pub enum ProjectType {
158    WebApplication,
159    ApiService,
160    CliTool,
161    Library,
162    MobileApp,
163    DesktopApp,
164    Microservice,
165    StaticSite,
166    Hybrid, // Multiple types
167    Unknown,
168}
169
170/// Represents build scripts and commands
171#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
172pub struct BuildScript {
173    pub name: String,
174    pub command: String,
175    pub description: Option<String>,
176    pub is_default: bool,
177}
178
179/// Type alias for dependency maps
180pub type DependencyMap = HashMap<String, String>;
181
182/// Types of project architectures
183#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
184pub enum ArchitectureType {
185    /// Single application/service
186    Monolithic,
187    /// Multiple services in one repository
188    Microservices,
189    /// Mixed approach with both
190    Hybrid,
191}
192
193/// Backward compatibility type alias
194pub type DetectedFramework = DetectedTechnology;
195
196/// Enhanced project analysis with proper technology classification and microservice support
197#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
198pub struct ProjectAnalysis {
199    pub project_root: PathBuf,
200    pub languages: Vec<DetectedLanguage>,
201    /// All detected technologies (frameworks, libraries, tools) with proper classification
202    pub technologies: Vec<DetectedTechnology>,
203    /// Legacy field for backward compatibility - will be populated from technologies
204    #[deprecated(note = "Use technologies field instead")]
205    pub frameworks: Vec<DetectedFramework>,
206    pub dependencies: DependencyMap,
207    pub entry_points: Vec<EntryPoint>,
208    pub ports: Vec<Port>,
209    pub environment_variables: Vec<EnvVar>,
210    pub project_type: ProjectType,
211    pub build_scripts: Vec<BuildScript>,
212    /// Individual service analyses for microservice architectures
213    pub services: Vec<ServiceAnalysis>,
214    /// Whether this is a monolithic project or microservice architecture
215    pub architecture_type: ArchitectureType,
216    pub analysis_metadata: AnalysisMetadata,
217}
218
219/// Metadata about the analysis process
220#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
221pub struct AnalysisMetadata {
222    pub timestamp: String,
223    pub analyzer_version: String,
224    pub analysis_duration_ms: u64,
225    pub files_analyzed: usize,
226    pub confidence_score: f32,
227}
228
229/// Configuration for project analysis
230#[derive(Debug, Clone)]
231pub struct AnalysisConfig {
232    pub include_dev_dependencies: bool,
233    pub deep_analysis: bool,
234    pub ignore_patterns: Vec<String>,
235    pub max_file_size: usize,
236}
237
238impl Default for AnalysisConfig {
239    fn default() -> Self {
240        Self {
241            include_dev_dependencies: false,
242            deep_analysis: true,
243            ignore_patterns: vec![
244                "node_modules".to_string(),
245                ".git".to_string(),
246                "target".to_string(),
247                "build".to_string(),
248                ".next".to_string(),
249                "dist".to_string(),
250            ],
251            max_file_size: 1024 * 1024, // 1MB
252        }
253    }
254}
255
256/// Analyzes a project directory to detect languages, frameworks, and dependencies.
257/// 
258/// # Arguments
259/// * `path` - The root directory of the project to analyze
260/// 
261/// # Returns
262/// A `ProjectAnalysis` containing detected components or an error
263/// 
264/// # Examples
265/// ```no_run
266/// use syncable_cli::analyzer::analyze_project;
267/// use std::path::Path;
268/// 
269/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
270/// let analysis = analyze_project(Path::new("./my-project"))?;
271/// println!("Languages: {:?}", analysis.languages);
272/// # Ok(())
273/// # }
274/// ```
275pub fn analyze_project(path: &Path) -> Result<ProjectAnalysis> {
276    analyze_project_with_config(path, &AnalysisConfig::default())
277}
278
279/// Analyzes a project with custom configuration
280pub fn analyze_project_with_config(path: &Path, config: &AnalysisConfig) -> Result<ProjectAnalysis> {
281    let start_time = std::time::Instant::now();
282    
283    // Validate project path
284    let project_root = crate::common::file_utils::validate_project_path(path)?;
285    
286    log::info!("Starting analysis of project: {}", project_root.display());
287    
288    // Collect project files
289    let files = crate::common::file_utils::collect_project_files(&project_root, config)?;
290    log::debug!("Found {} files to analyze", files.len());
291    
292    // Perform parallel analysis
293    let languages = language_detector::detect_languages(&files, config)?;
294    let frameworks = framework_detector::detect_frameworks(&project_root, &languages, config)?;
295    let dependencies = dependency_parser::parse_dependencies(&project_root, &languages, config)?;
296    let context = project_context::analyze_context(&project_root, &languages, &frameworks, config)?;
297    
298    let duration = start_time.elapsed();
299    let confidence = calculate_confidence_score(&languages, &frameworks);
300    
301    #[allow(deprecated)]
302    let analysis = ProjectAnalysis {
303        project_root,
304        languages,
305        technologies: frameworks.clone(), // New field with proper technology classification
306        frameworks, // Backward compatibility
307        dependencies,
308        entry_points: context.entry_points,
309        ports: context.ports,
310        environment_variables: context.environment_variables,
311        project_type: context.project_type,
312        build_scripts: context.build_scripts,
313        services: vec![], // TODO: Implement microservice detection
314        architecture_type: ArchitectureType::Monolithic, // TODO: Detect architecture type
315        analysis_metadata: AnalysisMetadata {
316            timestamp: Utc::now().to_rfc3339(),
317            analyzer_version: env!("CARGO_PKG_VERSION").to_string(),
318            analysis_duration_ms: duration.as_millis() as u64,
319            files_analyzed: files.len(),
320            confidence_score: confidence,
321        },
322    };
323    
324    log::info!("Analysis completed in {}ms", duration.as_millis());
325    Ok(analysis)
326}
327
328/// Calculate overall confidence score based on detection results
329fn calculate_confidence_score(
330    languages: &[DetectedLanguage],
331    frameworks: &[DetectedFramework],
332) -> f32 {
333    if languages.is_empty() {
334        return 0.0;
335    }
336    
337    let lang_confidence: f32 = languages.iter().map(|l| l.confidence).sum::<f32>() / languages.len() as f32;
338    let framework_confidence: f32 = if frameworks.is_empty() {
339        0.5 // Neutral score if no frameworks detected
340    } else {
341        frameworks.iter().map(|f| f.confidence).sum::<f32>() / frameworks.len() as f32
342    };
343    
344    (lang_confidence * 0.7 + framework_confidence * 0.3).min(1.0)
345}
346
347#[cfg(test)]
348mod tests {
349    use super::*;
350    
351    #[test]
352    fn test_confidence_calculation() {
353        let languages = vec![
354            DetectedLanguage {
355                name: "Rust".to_string(),
356                version: Some("1.70.0".to_string()),
357                confidence: 0.9,
358                files: vec![],
359                main_dependencies: vec!["serde".to_string(), "tokio".to_string()],
360                dev_dependencies: vec!["assert_cmd".to_string()],
361                package_manager: Some("cargo".to_string()),
362            }
363        ];
364        
365        let technologies = vec![
366            DetectedTechnology {
367                name: "Actix Web".to_string(),
368                version: Some("4.0".to_string()),
369                category: TechnologyCategory::BackendFramework,
370                confidence: 0.8,
371                requires: vec!["serde".to_string(), "tokio".to_string()],
372                conflicts_with: vec![],
373                is_primary: true,
374            }
375        ];
376        
377        let frameworks = technologies.clone(); // For backward compatibility
378        
379        let score = calculate_confidence_score(&languages, &frameworks);
380        assert!(score > 0.8);
381        assert!(score <= 1.0);
382    }
383    
384    #[test]
385    fn test_empty_analysis() {
386        let languages = vec![];
387        let frameworks = vec![];
388        let score = calculate_confidence_score(&languages, &frameworks);
389        assert_eq!(score, 0.0);
390    }
391}