debtmap 0.16.3

Code complexity and technical debt analyzer
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
//! # Struct Pattern Detection (Pure Core)
//!
//! Pure functions for detecting common Rust patterns that should not be
//! flagged as god objects. Implements pattern recognition to reduce false
//! positives.
//!
//! ## Stillwater Architecture
//!
//! This module is part of the **Pure Core** - all functions are deterministic
//! with no side effects. Pattern detection is a pure transformation of metrics.
//!
//! ## Recognized Patterns
//!
//! - **Config Pattern**: Builder/factory methods for configuration presets
//! - **DTO Pattern**: Data Transfer Objects with minimal behavior
//! - **Aggregate Root**: Domain entities with many fields but cohesive responsibility
//!
//! ## Parallel to builder_pattern.rs
//!
//! This module follows the same architectural pattern as `builder_pattern.rs`,
//! providing organization-level pattern detection that can be used by multiple
//! analyzers (currently used by god_object detector).

use crate::organization::god_object::ast_visitor::TypeAnalysis;

/// Pattern classification for struct types.
///
/// Used to distinguish acceptable patterns from genuine god objects.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum StructPattern {
    /// Configuration struct with factory methods
    Config,
    /// Data Transfer Object - data container with minimal behavior
    DataTransferObject,
    /// Aggregate Root - domain entity with many fields but single responsibility
    AggregateRoot,
    /// No recognizable pattern - standard struct
    Standard,
}

/// Comprehensive pattern analysis result.
#[derive(Debug, Clone)]
pub struct PatternAnalysis {
    /// Detected pattern type
    pub pattern: StructPattern,
    /// Confidence in detection (0.0 - 1.0)
    pub confidence: f64,
    /// Evidence supporting this classification
    pub evidence: Vec<String>,
    /// Whether this pattern should skip god object detection
    pub skip_god_object_check: bool,
}

/// Detect struct pattern from metrics (pure function).
///
/// Analyzes method names, field count, and method-to-field ratio to identify
/// common Rust patterns that are not god objects despite high metric counts.
///
/// # Arguments
///
/// * `type_info` - Collected type information from AST
/// * `responsibilities` - Number of detected responsibilities
///
/// # Returns
///
/// Pattern analysis with classification and confidence score
///
/// # Examples
///
/// ```
/// use debtmap::organization::struct_patterns::{detect_pattern, StructPattern};
/// use debtmap::organization::god_object::ast_visitor::TypeAnalysis;
/// use debtmap::common::{SourceLocation, LocationConfidence};
///
/// let type_analysis = TypeAnalysis {
///     name: "AppConfig".to_string(),
///     method_count: 5,
///     field_count: 8,
///     methods: vec!["strict".into(), "balanced".into(), "lenient".into()],
///     fields: vec![],
///     field_types: vec![],
///     trait_implementations: 0,
///     location: SourceLocation {
///         line: 1,
///         column: Some(1),
///         end_line: None,
///         end_column: None,
///         confidence: LocationConfidence::Exact,
///     },
///     impl_locations: vec![],
/// };
///
/// let analysis = detect_pattern(&type_analysis, 1);
/// assert_eq!(analysis.pattern, StructPattern::Config);
/// assert!(analysis.skip_god_object_check);
/// ```
pub fn detect_pattern(type_analysis: &TypeAnalysis, responsibilities: usize) -> PatternAnalysis {
    // Try patterns in order of specificity
    if let Some(analysis) = detect_config_pattern(type_analysis, responsibilities) {
        return analysis;
    }

    if let Some(analysis) = detect_dto_pattern(type_analysis, responsibilities) {
        return analysis;
    }

    if let Some(analysis) = detect_aggregate_root_pattern(type_analysis, responsibilities) {
        return analysis;
    }

    // Default: standard struct, no special handling
    PatternAnalysis {
        pattern: StructPattern::Standard,
        confidence: 1.0,
        evidence: vec![],
        skip_god_object_check: false,
    }
}

/// Detect configuration pattern (pure function).
///
/// Indicators:
/// - Name contains "Config", "Settings", "Options"
/// - Factory methods: strict(), balanced(), lenient(), default()
/// - Low field count (< 10)
/// - Low method count (< 10)
/// - Single responsibility
fn detect_config_pattern(
    type_analysis: &TypeAnalysis,
    responsibilities: usize,
) -> Option<PatternAnalysis> {
    let mut evidence = Vec::new();
    let mut confidence = 0.0;

    // Check name
    let name_lower = type_analysis.name.to_lowercase();
    if name_lower.contains("config")
        || name_lower.contains("settings")
        || name_lower.contains("options")
    {
        evidence.push("Name indicates configuration struct".to_string());
        confidence += 0.3;
    }

    // Check for factory methods
    let factory_methods = ["strict", "balanced", "lenient", "default", "new"];
    let has_factory = type_analysis
        .methods
        .iter()
        .any(|m| factory_methods.contains(&m.as_str()));

    if has_factory {
        let found: Vec<_> = type_analysis
            .methods
            .iter()
            .filter(|m| factory_methods.contains(&m.as_str()))
            .collect();
        evidence.push(format!("Has factory methods: {:?}", found));
        confidence += 0.4;
    }

    // Check metric constraints
    if type_analysis.field_count <= 10 && type_analysis.method_count <= 10 {
        evidence.push(format!(
            "Reasonable size: {} fields, {} methods",
            type_analysis.field_count, type_analysis.method_count
        ));
        confidence += 0.2;
    }

    // Single responsibility is expected for config
    if responsibilities <= 1 {
        evidence.push("Single responsibility (configuration)".to_string());
        confidence += 0.1;
    }

    // Need strong evidence (>= 0.6) to classify as config
    if confidence >= 0.6 {
        Some(PatternAnalysis {
            pattern: StructPattern::Config,
            confidence,
            evidence,
            skip_god_object_check: true,
        })
    } else {
        None
    }
}

/// Detect Data Transfer Object pattern (pure function).
///
/// Indicators:
/// - Many fields (>= 15)
/// - Minimal methods (<= 3)
/// - Low method-to-field ratio (< 0.2)
/// - Single responsibility
/// - Name patterns: *Data, *Dto, *Item, *Record, *Result, *Metrics
fn detect_dto_pattern(
    type_analysis: &TypeAnalysis,
    responsibilities: usize,
) -> Option<PatternAnalysis> {
    let mut evidence = Vec::new();
    let mut confidence = 0.0;

    // High field count is expected for DTOs
    if type_analysis.field_count >= 15 {
        evidence.push(format!("High field count: {}", type_analysis.field_count));
        confidence += 0.3;
    }

    // Minimal behavior
    if type_analysis.method_count <= 3 {
        evidence.push(format!(
            "Minimal methods ({}), indicating data container",
            type_analysis.method_count
        ));
        confidence += 0.3;
    }

    // Check method-to-field ratio
    let ratio = type_analysis.method_count as f64 / type_analysis.field_count.max(1) as f64;
    if ratio < 0.2 {
        evidence.push(format!(
            "Low method-to-field ratio ({:.2}), data-heavy",
            ratio
        ));
        confidence += 0.2;
    }

    // Single responsibility (all fields relate to one concept)
    if responsibilities <= 1 {
        evidence.push("Single conceptual responsibility".to_string());
        confidence += 0.2;
    }

    // Check name patterns
    let name_lower = type_analysis.name.to_lowercase();
    let dto_suffixes = [
        "data", "dto", "item", "record", "result", "metrics", "analysis",
    ];
    if dto_suffixes.iter().any(|s| name_lower.ends_with(s)) {
        evidence.push(format!("Name pattern suggests DTO: {}", type_analysis.name));
        confidence += 0.1;
    }

    // Need strong evidence (>= 0.7) for DTO classification
    if confidence >= 0.7 {
        Some(PatternAnalysis {
            pattern: StructPattern::DataTransferObject,
            confidence,
            evidence,
            skip_god_object_check: true,
        })
    } else {
        None
    }
}

/// Detect Aggregate Root pattern (pure function).
///
/// Indicators:
/// - Many fields but single cohesive responsibility
/// - Moderate method count (related to managing the aggregate)
/// - Name patterns: domain entities
///
/// This is more lenient than DTO - represents a valid domain design
/// where a single entity legitimately needs many fields.
fn detect_aggregate_root_pattern(
    type_analysis: &TypeAnalysis,
    responsibilities: usize,
) -> Option<PatternAnalysis> {
    let mut evidence = Vec::new();
    let mut confidence = 0.0;

    // Must have single responsibility
    if responsibilities > 1 {
        return None; // Multiple responsibilities = not an aggregate root
    }

    evidence.push("Single responsibility detected".to_string());
    confidence += 0.4;

    // High field count is REQUIRED for aggregate root (not optional)
    if type_analysis.field_count < 10 {
        return None; // Too few fields to be an aggregate root
    }

    evidence.push(format!(
        "Complex domain entity: {} fields",
        type_analysis.field_count
    ));
    confidence += 0.2;

    // Moderate methods (domain operations)
    if type_analysis.method_count >= 5 && type_analysis.method_count <= 20 {
        evidence.push(format!(
            "Moderate method count ({}), within domain complexity",
            type_analysis.method_count
        ));
        confidence += 0.2;
    }

    // Higher tolerance for aggregate roots - they're often complex
    // but if single responsibility, likely valid domain design
    if confidence >= 0.6 {
        Some(PatternAnalysis {
            pattern: StructPattern::AggregateRoot,
            confidence,
            evidence,
            skip_god_object_check: false, // Still check, but with context
        })
    } else {
        None
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::common::{LocationConfidence, SourceLocation};

    fn make_type_analysis(
        name: &str,
        methods: Vec<&str>,
        method_count: usize,
        field_count: usize,
    ) -> TypeAnalysis {
        TypeAnalysis {
            name: name.to_string(),
            method_count,
            field_count,
            methods: methods.into_iter().map(String::from).collect(),
            fields: vec![],
            field_types: vec![],
            trait_implementations: 0,
            location: SourceLocation {
                line: 1,
                column: Some(1),
                end_line: None,
                end_column: None,
                confidence: LocationConfidence::Exact,
            },
            impl_locations: vec![],
        }
    }

    #[test]
    fn test_config_pattern_detected() {
        let type_analysis = make_type_analysis(
            "FunctionalAnalysisConfig",
            vec!["strict", "balanced", "lenient", "should_analyze"],
            4,
            5,
        );

        let analysis = detect_pattern(&type_analysis, 1);
        assert_eq!(analysis.pattern, StructPattern::Config);
        assert!(analysis.confidence >= 0.6);
        assert!(analysis.skip_god_object_check);
    }

    #[test]
    fn test_dto_pattern_detected() {
        let type_analysis =
            make_type_analysis("UnifiedDebtItem", vec!["with_pattern_analysis"], 1, 35);

        let analysis = detect_pattern(&type_analysis, 1);
        assert_eq!(analysis.pattern, StructPattern::DataTransferObject);
        assert!(analysis.confidence >= 0.7);
        assert!(analysis.skip_god_object_check);
    }

    #[test]
    fn test_genuine_god_object_not_dto() {
        // Many fields AND many methods AND multiple responsibilities
        let type_analysis = make_type_analysis(
            "UserManager",
            vec![
                "create_user",
                "delete_user",
                "send_email",
                "log_activity",
                "validate_input",
                "render_template",
            ],
            25,
            20,
        );

        let analysis = detect_pattern(&type_analysis, 5); // Multiple responsibilities
        assert_eq!(analysis.pattern, StructPattern::Standard);
        assert!(!analysis.skip_god_object_check);
    }

    #[test]
    fn test_aggregate_root_pattern() {
        let type_analysis = make_type_analysis(
            "Order",
            vec!["add_item", "calculate_total", "apply_discount", "validate"],
            8,
            12,
        );

        let analysis = detect_pattern(&type_analysis, 1);
        assert_eq!(analysis.pattern, StructPattern::AggregateRoot);
        assert!(!analysis.skip_god_object_check); // Still check but with context
    }

    #[test]
    fn test_standard_struct() {
        let type_analysis = make_type_analysis("Helper", vec!["process"], 5, 3);

        let analysis = detect_pattern(&type_analysis, 1);
        assert_eq!(analysis.pattern, StructPattern::Standard);
    }
}