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