implementation.rs•38.3 kB
//! Implementation pattern recognition for design patterns
#[cfg(feature = "napi-bindings")]
use napi_derive::napi;
use crate::patterns::types::{Pattern, PatternExample, ImplementationPattern, PatternExtractor};
use crate::types::{ParseError, LineRange, SemanticConcept};
use std::collections::HashMap;
use regex::Regex;
use walkdir::WalkDir;
use std::fs;
/// Analyzer for detecting implementation patterns (design patterns)
#[cfg_attr(feature = "napi-bindings", napi)]
pub struct ImplementationPatternAnalyzer {
patterns: HashMap<String, ImplementationPattern>,
pattern_signatures: HashMap<String, PatternSignature>,
}
#[derive(Debug, Clone)]
struct PatternSignature {
required_methods: Vec<String>,
optional_methods: Vec<String>,
class_characteristics: Vec<String>,
code_patterns: Vec<String>,
confidence_threshold: f64,
}
#[derive(Debug, Clone)]
struct PatternMatch {
pattern_name: String,
confidence: f64,
evidence: Vec<String>,
location: String,
}
#[cfg_attr(feature = "napi-bindings", napi)]
impl ImplementationPatternAnalyzer {
#[cfg_attr(feature = "napi-bindings", napi(constructor))]
pub fn new() -> Self {
let mut analyzer = ImplementationPatternAnalyzer {
patterns: HashMap::new(),
pattern_signatures: HashMap::new(),
};
analyzer.initialize_pattern_signatures();
analyzer
}
/// Initialize signatures for common design patterns
fn initialize_pattern_signatures(&mut self) {
// Singleton Pattern
self.pattern_signatures.insert("Singleton".to_string(), PatternSignature {
required_methods: vec!["getInstance".to_string()],
optional_methods: vec!["constructor".to_string(), "__construct".to_string()],
class_characteristics: vec!["static_instance".to_string(), "private_constructor".to_string()],
code_patterns: vec![
r"private\s+static\s+\w*instance".to_string(),
r"getInstance\(\)".to_string(),
r"private\s+\w*\(\)".to_string(), // private constructor
],
confidence_threshold: 0.7,
});
// Factory Pattern
self.pattern_signatures.insert("Factory".to_string(), PatternSignature {
required_methods: vec!["create".to_string(), "make".to_string(), "build".to_string()],
optional_methods: vec!["factory".to_string()],
class_characteristics: vec!["creator".to_string(), "product".to_string()],
code_patterns: vec![
r"create\w*\(\)".to_string(),
r"make\w*\(\)".to_string(),
r"Factory".to_string(),
],
confidence_threshold: 0.6,
});
// Observer Pattern
self.pattern_signatures.insert("Observer".to_string(), PatternSignature {
required_methods: vec!["notify".to_string(), "update".to_string(), "subscribe".to_string()],
optional_methods: vec!["unsubscribe".to_string(), "addListener".to_string(), "removeListener".to_string()],
class_characteristics: vec!["subject".to_string(), "observer".to_string(), "listeners".to_string()],
code_patterns: vec![
r"notify\w*\(\)".to_string(),
r"update\(\)".to_string(),
r"subscribe\(\)".to_string(),
r"addEventListener".to_string(),
],
confidence_threshold: 0.7,
});
// Builder Pattern
self.pattern_signatures.insert("Builder".to_string(), PatternSignature {
required_methods: vec!["build".to_string(), "with".to_string(), "set".to_string()],
optional_methods: vec!["create".to_string(), "builder".to_string()],
class_characteristics: vec!["builder".to_string(), "director".to_string()],
code_patterns: vec![
r"\.with\w+\(".to_string(),
r"\.set\w+\(".to_string(),
r"\.build\(\)".to_string(),
r"Builder".to_string(),
],
confidence_threshold: 0.6,
});
// Strategy Pattern
self.pattern_signatures.insert("Strategy".to_string(), PatternSignature {
required_methods: vec!["execute".to_string(), "apply".to_string(), "process".to_string()],
optional_methods: vec!["strategy".to_string(), "algorithm".to_string()],
class_characteristics: vec!["strategy".to_string(), "context".to_string()],
code_patterns: vec![
r"execute\(\)".to_string(),
r"Strategy".to_string(),
r"setStrategy\(".to_string(),
],
confidence_threshold: 0.6,
});
// Dependency Injection Pattern
self.pattern_signatures.insert("DependencyInjection".to_string(), PatternSignature {
required_methods: vec!["inject".to_string(), "provide".to_string(), "register".to_string()],
optional_methods: vec!["bind".to_string(), "container".to_string()],
class_characteristics: vec!["injector".to_string(), "container".to_string(), "provider".to_string()],
code_patterns: vec![
r"@inject".to_string(),
r"@Injectable".to_string(),
r"container\.get\(".to_string(),
r"DI".to_string(),
],
confidence_threshold: 0.7,
});
// Decorator Pattern
self.pattern_signatures.insert("Decorator".to_string(), PatternSignature {
required_methods: vec!["wrap".to_string(), "decorate".to_string()],
optional_methods: vec!["unwrap".to_string()],
class_characteristics: vec!["decorator".to_string(), "wrapper".to_string()],
code_patterns: vec![
r"@\w+".to_string(), // Decorator syntax
r"Decorator".to_string(),
r"wrap\(".to_string(),
],
confidence_threshold: 0.6,
});
// Command Pattern
self.pattern_signatures.insert("Command".to_string(), PatternSignature {
required_methods: vec!["execute".to_string(), "undo".to_string()],
optional_methods: vec!["redo".to_string(), "command".to_string()],
class_characteristics: vec!["command".to_string(), "invoker".to_string(), "receiver".to_string()],
code_patterns: vec![
r"execute\(\)".to_string(),
r"undo\(\)".to_string(),
r"Command".to_string(),
],
confidence_threshold: 0.7,
});
// Adapter Pattern
self.pattern_signatures.insert("Adapter".to_string(), PatternSignature {
required_methods: vec!["adapt".to_string(), "convert".to_string()],
optional_methods: vec!["wrap".to_string()],
class_characteristics: vec!["adapter".to_string(), "adaptee".to_string()],
code_patterns: vec![
r"Adapter".to_string(),
r"adapt\(".to_string(),
],
confidence_threshold: 0.6,
});
}
/// Analyze semantic concepts for implementation patterns
pub fn analyze_concepts(&mut self, concepts: &[SemanticConcept]) -> Result<Vec<Pattern>, ParseError> {
let mut detected_patterns = Vec::new();
let pattern_matches = self.detect_patterns_in_concepts(concepts)?;
for pattern_match in pattern_matches {
if pattern_match.confidence >= self.pattern_signatures
.get(&pattern_match.pattern_name)
.map(|s| s.confidence_threshold)
.unwrap_or(0.5) {
let examples = self.create_examples_for_pattern(&pattern_match, concepts);
let pattern = Pattern {
id: format!("implementation_{}", pattern_match.pattern_name.to_lowercase()),
pattern_type: "implementation".to_string(),
description: format!(
"{} pattern detected with {:.1}% confidence",
pattern_match.pattern_name,
pattern_match.confidence * 100.0
),
frequency: pattern_match.evidence.len() as u32,
confidence: pattern_match.confidence,
examples,
contexts: vec!["design_pattern".to_string()],
};
detected_patterns.push(pattern);
// Store in internal patterns
let impl_pattern = ImplementationPattern {
pattern_type: pattern_match.pattern_name.clone(),
frequency: pattern_match.evidence.len() as u32,
code_signatures: pattern_match.evidence.clone(),
confidence: pattern_match.confidence,
};
self.patterns.insert(pattern_match.pattern_name.clone(), impl_pattern);
}
}
Ok(detected_patterns)
}
/// Detect anti-patterns in implementation
pub fn detect_antipatterns(&self, concepts: &[SemanticConcept]) -> Vec<String> {
let mut antipatterns = Vec::new();
antipatterns.extend(self.detect_god_object_antipattern(concepts));
antipatterns.extend(self.detect_spaghetti_code_antipattern(concepts));
antipatterns.extend(self.detect_copy_paste_antipattern(concepts));
antipatterns.extend(self.detect_magic_number_antipattern(concepts));
antipatterns.extend(self.detect_long_parameter_list_antipattern(concepts));
antipatterns
}
/// Generate recommendations based on detected patterns
pub fn generate_recommendations(&self, concepts: &[SemanticConcept]) -> Vec<String> {
let mut recommendations = Vec::new();
// Check for missing patterns that could be beneficial
if self.should_suggest_singleton(concepts) {
recommendations.push("Consider using Singleton pattern for global state management".to_string());
}
if self.should_suggest_factory(concepts) {
recommendations.push("Consider using Factory pattern for object creation complexity".to_string());
}
if self.should_suggest_observer(concepts) {
recommendations.push("Consider using Observer pattern for event handling".to_string());
}
if self.should_suggest_strategy(concepts) {
recommendations.push("Consider using Strategy pattern to reduce conditional complexity".to_string());
}
if self.should_suggest_dependency_injection(concepts) {
recommendations.push("Consider using Dependency Injection for better testability".to_string());
}
// Anti-pattern recommendations
let antipatterns = self.detect_antipatterns(concepts);
if !antipatterns.is_empty() {
recommendations.push("Address detected anti-patterns to improve code quality".to_string());
}
if recommendations.is_empty() {
recommendations.push("Implementation patterns look good! Consider documenting design decisions".to_string());
}
recommendations
}
/// Analyze code files for pattern signatures
pub fn analyze_code_files(&mut self, path: &str) -> Result<Vec<Pattern>, ParseError> {
let mut detected_patterns = Vec::new();
for entry in WalkDir::new(path).into_iter().filter_map(|e| e.ok()) {
if entry.file_type().is_file() {
let file_path = entry.path();
if let Some(extension) = file_path.extension().and_then(|s| s.to_str()) {
if matches!(extension.to_lowercase().as_str(), "js" | "ts" | "jsx" | "tsx" | "rs" | "py" | "java" | "cs" | "cpp" | "c") {
if let Ok(content) = fs::read_to_string(file_path) {
let patterns = self.detect_patterns_in_code(&content, file_path.to_string_lossy().as_ref())?;
detected_patterns.extend(patterns);
}
}
}
}
}
Ok(detected_patterns)
}
/// Detect patterns in concepts using semantic analysis
fn detect_patterns_in_concepts(&self, concepts: &[SemanticConcept]) -> Result<Vec<PatternMatch>, ParseError> {
let mut pattern_matches = Vec::new();
for (pattern_name, signature) in &self.pattern_signatures {
let mut evidence = Vec::new();
let mut confidence_scores = Vec::new();
// Check for required methods in concepts
let method_matches = self.find_method_matches(concepts, &signature.required_methods);
evidence.extend(method_matches.iter().map(|m| format!("Method: {}", m)));
if !method_matches.is_empty() {
confidence_scores.push(0.4 * (method_matches.len() as f64 / signature.required_methods.len() as f64));
}
// Check for optional methods (bonus confidence)
if !signature.optional_methods.is_empty() {
let optional_matches = self.find_method_matches(concepts, &signature.optional_methods);
evidence.extend(optional_matches.iter().map(|m| format!("Optional Method: {}", m)));
if !optional_matches.is_empty() {
// Optional methods provide a confidence boost but aren't required
confidence_scores.push(0.15 * (optional_matches.len() as f64 / signature.optional_methods.len() as f64));
}
}
// Check for class characteristics
let class_matches = self.find_class_characteristic_matches(concepts, &signature.class_characteristics);
evidence.extend(class_matches.iter().map(|c| format!("Characteristic: {}", c)));
if !class_matches.is_empty() {
confidence_scores.push(0.3 * (class_matches.len() as f64 / signature.class_characteristics.len() as f64));
}
// Check naming patterns
let naming_matches = self.find_naming_pattern_matches(concepts, pattern_name);
evidence.extend(naming_matches.iter().map(|n| format!("Naming: {}", n)));
if !naming_matches.is_empty() {
confidence_scores.push(0.3);
}
let total_confidence: f64 = confidence_scores.iter().sum();
if total_confidence >= signature.confidence_threshold && !evidence.is_empty() {
pattern_matches.push(PatternMatch {
pattern_name: pattern_name.clone(),
confidence: total_confidence,
evidence,
location: "multiple_concepts".to_string(),
});
}
}
Ok(pattern_matches)
}
/// Detect patterns in code using regex patterns
fn detect_patterns_in_code(&self, code: &str, file_path: &str) -> Result<Vec<Pattern>, ParseError> {
let mut detected_patterns = Vec::new();
for (pattern_name, signature) in &self.pattern_signatures {
let mut evidence = Vec::new();
let mut confidence = 0.0;
// Check code patterns using regex
for code_pattern in &signature.code_patterns {
if let Ok(regex) = Regex::new(code_pattern) {
let matches: Vec<_> = regex.find_iter(code).collect();
if !matches.is_empty() {
evidence.extend(matches.iter().map(|m| format!("Code pattern: {}", m.as_str())));
confidence += 0.2;
}
}
}
// Check for method names in the code
for method in &signature.required_methods {
if code.contains(method) {
evidence.push(format!("Method found: {}", method));
confidence += 0.2;
}
}
if confidence >= signature.confidence_threshold && !evidence.is_empty() {
let examples = vec![PatternExample {
code: evidence.join(", "),
file_path: file_path.to_string(),
line_range: LineRange { start: 1, end: 1 },
}];
detected_patterns.push(Pattern {
id: format!("implementation_{}", pattern_name.to_lowercase()),
pattern_type: "implementation".to_string(),
description: format!("{} pattern detected in code", pattern_name),
frequency: evidence.len() as u32,
confidence,
examples,
contexts: vec!["code_analysis".to_string()],
});
}
}
Ok(detected_patterns)
}
/// Find method matches in concepts
fn find_method_matches(&self, concepts: &[SemanticConcept], required_methods: &[String]) -> Vec<String> {
let mut matches = Vec::new();
for concept in concepts {
if concept.concept_type == "method" || concept.concept_type == "function" {
for required_method in required_methods {
if concept.name.to_lowercase().contains(&required_method.to_lowercase()) ||
self.is_method_variant(&concept.name, required_method) {
matches.push(concept.name.clone());
break;
}
}
}
}
matches
}
/// Find class characteristic matches
fn find_class_characteristic_matches(&self, concepts: &[SemanticConcept], characteristics: &[String]) -> Vec<String> {
let mut matches = Vec::new();
for concept in concepts {
for characteristic in characteristics {
if concept.name.to_lowercase().contains(&characteristic.to_lowercase()) ||
concept.concept_type.to_lowercase().contains(&characteristic.to_lowercase()) ||
concept.metadata.values().any(|v| v.to_lowercase().contains(&characteristic.to_lowercase())) {
matches.push(characteristic.clone());
}
}
}
matches
}
/// Find naming pattern matches
fn find_naming_pattern_matches(&self, concepts: &[SemanticConcept], pattern_name: &str) -> Vec<String> {
let mut matches = Vec::new();
let pattern_lower = pattern_name.to_lowercase();
for concept in concepts {
if concept.name.to_lowercase().contains(&pattern_lower) ||
concept.file_path.to_lowercase().contains(&pattern_lower) {
matches.push(concept.name.clone());
}
}
matches
}
/// Check if a method name is a variant of a required method
fn is_method_variant(&self, method_name: &str, required_method: &str) -> bool {
let method_lower = method_name.to_lowercase();
let required_lower = required_method.to_lowercase();
// Check for common variants
match required_lower.as_str() {
"getinstance" => method_lower.contains("getinstance") || method_lower.contains("instance"),
"create" => method_lower.contains("create") || method_lower.contains("new") || method_lower.contains("make"),
"notify" => method_lower.contains("notify") || method_lower.contains("emit") || method_lower.contains("trigger"),
"update" => method_lower.contains("update") || method_lower.contains("refresh") || method_lower.contains("change"),
"build" => method_lower.contains("build") || method_lower.contains("construct") || method_lower.contains("assemble"),
_ => method_lower.contains(&required_lower),
}
}
/// Create examples for detected patterns
fn create_examples_for_pattern(&self, pattern_match: &PatternMatch, concepts: &[SemanticConcept]) -> Vec<PatternExample> {
let mut examples = Vec::new();
// Find concepts that contributed to this pattern match
for concept in concepts.iter().take(3) { // Limit to first 3 examples
if concept.name.to_lowercase().contains(&pattern_match.pattern_name.to_lowercase()) ||
pattern_match.evidence.iter().any(|e| e.contains(&concept.name)) {
examples.push(PatternExample {
code: format!("{} {}", concept.concept_type, concept.name),
file_path: concept.file_path.clone(),
line_range: concept.line_range.clone(),
});
}
}
if examples.is_empty() {
// Fallback example
examples.push(PatternExample {
code: pattern_match.evidence.first().cloned().unwrap_or_else(|| "Pattern detected".to_string()),
file_path: pattern_match.location.clone(),
line_range: LineRange { start: 1, end: 1 },
});
}
examples
}
// Anti-pattern detection methods
fn detect_god_object_antipattern(&self, concepts: &[SemanticConcept]) -> Vec<String> {
let mut antipatterns = Vec::new();
let mut class_method_counts: HashMap<String, usize> = HashMap::new();
for concept in concepts {
if concept.concept_type == "class" {
let method_count = concepts.iter()
.filter(|c| c.concept_type == "method" && c.file_path == concept.file_path)
.count();
class_method_counts.insert(concept.name.clone(), method_count);
if method_count > 20 {
antipatterns.push(format!(
"God Object anti-pattern: Class '{}' has {} methods ({}:{})",
concept.name, method_count, concept.file_path, concept.line_range.start
));
}
}
}
antipatterns
}
fn detect_spaghetti_code_antipattern(&self, concepts: &[SemanticConcept]) -> Vec<String> {
let mut antipatterns = Vec::new();
// Check for functions with too many dependencies
for concept in concepts {
if concept.concept_type == "function" || concept.concept_type == "method" {
let dependency_count = concept.relationships.len();
if dependency_count > 15 {
antipatterns.push(format!(
"Spaghetti Code: Function '{}' has {} dependencies ({}:{})",
concept.name, dependency_count, concept.file_path, concept.line_range.start
));
}
}
}
antipatterns
}
fn detect_copy_paste_antipattern(&self, concepts: &[SemanticConcept]) -> Vec<String> {
let mut antipatterns = Vec::new();
let mut similar_names: HashMap<String, Vec<&SemanticConcept>> = HashMap::new();
// Group concepts by similar names
for concept in concepts {
let name_base = self.extract_name_base(&concept.name);
similar_names.entry(name_base).or_default().push(concept);
}
// Check for potential copy-paste patterns
for (base_name, group) in similar_names {
if group.len() > 3 && base_name.len() > 3 {
let names: Vec<String> = group.iter().map(|c| c.name.clone()).collect();
antipatterns.push(format!(
"Potential Copy-Paste: {} similar functions found: {}",
group.len(),
names.join(", ")
));
}
}
antipatterns
}
fn detect_magic_number_antipattern(&self, concepts: &[SemanticConcept]) -> Vec<String> {
let mut antipatterns = Vec::new();
// This is a simplified check - in practice you'd analyze the actual code
for concept in concepts {
if concept.concept_type == "constant" {
// Check if it's a meaningful constant name or just a number
if concept.name.chars().all(|c| c.is_numeric() || c == '.' || c == '_') {
antipatterns.push(format!(
"Magic Number: Constant '{}' should have a descriptive name ({}:{})",
concept.name, concept.file_path, concept.line_range.start
));
}
}
}
antipatterns
}
fn detect_long_parameter_list_antipattern(&self, concepts: &[SemanticConcept]) -> Vec<String> {
let mut antipatterns = Vec::new();
for concept in concepts {
if concept.concept_type == "function" || concept.concept_type == "method" {
if let Some(params) = concept.metadata.get("parameters") {
if let Ok(param_count) = params.parse::<usize>() {
if param_count > 5 {
antipatterns.push(format!(
"Long Parameter List: Function '{}' has {} parameters ({}:{})",
concept.name, param_count, concept.file_path, concept.line_range.start
));
}
}
}
}
}
antipatterns
}
// Recommendation helper methods
fn should_suggest_singleton(&self, concepts: &[SemanticConcept]) -> bool {
// Check for global state or configuration management
let has_global_state = concepts.iter().any(|c|
c.name.to_lowercase().contains("config") ||
c.name.to_lowercase().contains("settings") ||
c.name.to_lowercase().contains("global")
);
let has_singleton = self.patterns.contains_key("Singleton");
has_global_state && !has_singleton
}
fn should_suggest_factory(&self, concepts: &[SemanticConcept]) -> bool {
// Check for complex object creation
let creation_complexity = concepts.iter()
.filter(|c| c.concept_type == "constructor" || c.name.to_lowercase().contains("new"))
.count();
let has_factory = self.patterns.contains_key("Factory");
creation_complexity > 3 && !has_factory
}
fn should_suggest_observer(&self, concepts: &[SemanticConcept]) -> bool {
// Check for event handling patterns
let has_events = concepts.iter().any(|c|
c.name.to_lowercase().contains("event") ||
c.name.to_lowercase().contains("listener") ||
c.name.to_lowercase().contains("callback")
);
let has_observer = self.patterns.contains_key("Observer");
has_events && !has_observer
}
fn should_suggest_strategy(&self, concepts: &[SemanticConcept]) -> bool {
// Check for conditional complexity
let has_complex_conditionals = concepts.iter().any(|c|
c.metadata.get("body").is_some_and(|body|
body.matches("if").count() > 5 || body.matches("switch").count() > 1
)
);
let has_strategy = self.patterns.contains_key("Strategy");
has_complex_conditionals && !has_strategy
}
fn should_suggest_dependency_injection(&self, concepts: &[SemanticConcept]) -> bool {
// Check for tight coupling
let high_coupling_count = concepts.iter()
.filter(|c| c.relationships.len() > 8)
.count();
let has_di = self.patterns.contains_key("DependencyInjection");
high_coupling_count > 2 && !has_di
}
/// Extract base name for similarity detection
fn extract_name_base(&self, name: &str) -> String {
// Remove common suffixes and prefixes
let mut base = name.to_lowercase();
// Remove numbers at the end
while base.chars().last().is_some_and(|c| c.is_numeric()) {
base.pop();
}
// Remove common suffixes
for suffix in &["test", "impl", "service", "controller", "handler"] {
if base.ends_with(suffix) {
base = base[..base.len() - suffix.len()].to_string();
break;
}
}
base
}
}
impl PatternExtractor for ImplementationPatternAnalyzer {
fn extract_patterns(&self, path: &str) -> Result<Vec<Pattern>, ParseError> {
let mut analyzer = self.clone();
analyzer.analyze_code_files(path)
}
}
impl Clone for ImplementationPatternAnalyzer {
fn clone(&self) -> Self {
ImplementationPatternAnalyzer {
patterns: self.patterns.clone(),
pattern_signatures: self.pattern_signatures.clone(),
}
}
}
impl Default for ImplementationPatternAnalyzer {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::collections::HashMap;
fn create_test_concept(name: &str, concept_type: &str, file_path: &str) -> SemanticConcept {
SemanticConcept {
id: format!("test_{}", name),
name: name.to_string(),
concept_type: concept_type.to_string(),
confidence: 0.8,
file_path: file_path.to_string(),
line_range: LineRange { start: 1, end: 10 },
relationships: HashMap::new(),
metadata: HashMap::new(),
}
}
#[test]
fn test_implementation_pattern_analyzer_creation() {
let analyzer = ImplementationPatternAnalyzer::new();
assert!(!analyzer.pattern_signatures.is_empty());
assert!(analyzer.pattern_signatures.contains_key("Singleton"));
assert!(analyzer.pattern_signatures.contains_key("Factory"));
assert!(analyzer.pattern_signatures.contains_key("Observer"));
}
#[test]
fn test_singleton_pattern_detection() {
let mut analyzer = ImplementationPatternAnalyzer::new();
let concepts = vec![
create_test_concept("getInstance", "method", "Singleton.js"),
create_test_concept("SingletonClass", "class", "Singleton.js"),
];
let patterns = analyzer.analyze_concepts(&concepts).unwrap();
let singleton_pattern = patterns.iter().find(|p| p.id.contains("singleton"));
assert!(singleton_pattern.is_some());
if let Some(pattern) = singleton_pattern {
assert_eq!(pattern.pattern_type, "implementation");
assert!(pattern.confidence > 0.0);
}
}
#[test]
fn test_factory_pattern_detection() {
let mut analyzer = ImplementationPatternAnalyzer::new();
let concepts = vec![
create_test_concept("createUser", "method", "UserFactory.js"),
create_test_concept("UserFactory", "class", "UserFactory.js"),
];
let patterns = analyzer.analyze_concepts(&concepts).unwrap();
let factory_pattern = patterns.iter().find(|p| p.id.contains("factory"));
assert!(factory_pattern.is_some());
}
#[test]
fn test_observer_pattern_detection() {
let mut analyzer = ImplementationPatternAnalyzer::new();
let concepts = vec![
create_test_concept("notify", "method", "Observable.js"),
create_test_concept("subscribe", "method", "Observable.js"),
create_test_concept("update", "method", "Observer.js"),
];
let patterns = analyzer.analyze_concepts(&concepts).unwrap();
let observer_pattern = patterns.iter().find(|p| p.id.contains("observer"));
assert!(observer_pattern.is_some());
}
#[test]
fn test_builder_pattern_detection() {
let mut analyzer = ImplementationPatternAnalyzer::new();
let concepts = vec![
create_test_concept("withName", "method", "UserBuilder.js"),
create_test_concept("setAge", "method", "UserBuilder.js"),
create_test_concept("build", "method", "UserBuilder.js"),
];
let patterns = analyzer.analyze_concepts(&concepts).unwrap();
let builder_pattern = patterns.iter().find(|p| p.id.contains("builder"));
assert!(builder_pattern.is_some());
}
#[test]
fn test_god_object_antipattern_detection() {
let analyzer = ImplementationPatternAnalyzer::new();
let mut concepts = vec![create_test_concept("GodClass", "class", "GodClass.js")];
// Add many methods to simulate God Object
for i in 0..25 {
concepts.push(create_test_concept(&format!("method{}", i), "method", "GodClass.js"));
}
let antipatterns = analyzer.detect_god_object_antipattern(&concepts);
assert!(!antipatterns.is_empty());
assert!(antipatterns[0].contains("God Object"));
}
#[test]
fn test_spaghetti_code_antipattern_detection() {
let analyzer = ImplementationPatternAnalyzer::new();
let mut concept = create_test_concept("complexFunction", "function", "test.js");
// Add many dependencies to simulate spaghetti code
for i in 0..20 {
concept.relationships.insert(format!("depends_{}", i), format!("dependency{}", i));
}
let concepts = vec![concept];
let antipatterns = analyzer.detect_spaghetti_code_antipattern(&concepts);
assert!(!antipatterns.is_empty());
assert!(antipatterns[0].contains("Spaghetti Code"));
}
#[test]
fn test_copy_paste_antipattern_detection() {
let analyzer = ImplementationPatternAnalyzer::new();
let concepts = vec![
create_test_concept("processUser1", "function", "test.js"),
create_test_concept("processUser2", "function", "test.js"),
create_test_concept("processUser3", "function", "test.js"),
create_test_concept("processUser4", "function", "test.js"),
];
let antipatterns = analyzer.detect_copy_paste_antipattern(&concepts);
assert!(!antipatterns.is_empty());
assert!(antipatterns[0].contains("Copy-Paste"));
}
#[test]
fn test_long_parameter_list_detection() {
let analyzer = ImplementationPatternAnalyzer::new();
let mut concept = create_test_concept("complexFunction", "function", "test.js");
concept.metadata.insert("parameters".to_string(), "8".to_string());
let concepts = vec![concept];
let antipatterns = analyzer.detect_long_parameter_list_antipattern(&concepts);
assert!(!antipatterns.is_empty());
assert!(antipatterns[0].contains("Long Parameter List"));
}
#[test]
fn test_method_variant_detection() {
let analyzer = ImplementationPatternAnalyzer::new();
assert!(analyzer.is_method_variant("getInstance", "getinstance"));
assert!(analyzer.is_method_variant("createUser", "create"));
assert!(analyzer.is_method_variant("notifyAll", "notify"));
assert!(analyzer.is_method_variant("updateState", "update"));
assert!(analyzer.is_method_variant("buildObject", "build"));
assert!(!analyzer.is_method_variant("randomMethod", "create"));
}
#[test]
fn test_recommendation_generation() {
let analyzer = ImplementationPatternAnalyzer::new();
let concepts = vec![
create_test_concept("GlobalConfig", "class", "config.js"),
create_test_concept("addEventListener", "function", "events.js"),
];
let recommendations = analyzer.generate_recommendations(&concepts);
assert!(!recommendations.is_empty());
// Should suggest Singleton for global state
assert!(recommendations.iter().any(|r| r.contains("Singleton")));
}
#[test]
fn test_name_base_extraction() {
let analyzer = ImplementationPatternAnalyzer::new();
assert_eq!(analyzer.extract_name_base("processUser1"), "processuser");
assert_eq!(analyzer.extract_name_base("userServiceImpl"), "user");
assert_eq!(analyzer.extract_name_base("handleEventTest"), "handleevent");
assert_eq!(analyzer.extract_name_base("dataController"), "data");
}
#[test]
fn test_pattern_confidence_calculation() {
let mut analyzer = ImplementationPatternAnalyzer::new();
let concepts = vec![
create_test_concept("getInstance", "method", "Singleton.js"),
create_test_concept("SingletonInstance", "field", "Singleton.js"),
];
let patterns = analyzer.analyze_concepts(&concepts).unwrap();
if let Some(pattern) = patterns.first() {
assert!(pattern.confidence >= 0.5);
assert!(pattern.confidence <= 1.0);
}
}
#[test]
fn test_multiple_pattern_detection() {
let mut analyzer = ImplementationPatternAnalyzer::new();
let concepts = vec![
// Singleton pattern
create_test_concept("getInstance", "method", "Singleton.js"),
// Factory pattern
create_test_concept("createUser", "method", "Factory.js"),
// Observer pattern
create_test_concept("notify", "method", "Observer.js"),
create_test_concept("subscribe", "method", "Observer.js"),
];
let patterns = analyzer.analyze_concepts(&concepts).unwrap();
assert!(patterns.len() >= 2);
let pattern_names: Vec<String> = patterns.iter().map(|p| p.id.clone()).collect();
assert!(pattern_names.iter().any(|name| name.contains("singleton") || name.contains("factory") || name.contains("observer")));
}
}