howmany 3.0.0

A blazingly fast, intelligent code analysis tool with parallel processing, caching, and beautiful visualizations
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
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
use super::types::{
    ComplexityLevel, FunctionComplexityDetail, FunctionInfo, QualityMetrics, StructureInfo,
};
use crate::core::types::{CodeStats, FileStats};

/// Quality metrics calculator
pub struct QualityCalculator;

impl QualityCalculator {
    pub fn new() -> Self {
        Self
    }

    /// Calculate code health metrics for practical developer insights
    pub fn calculate_quality_metrics(
        &self,
        functions: &[FunctionInfo],
        file_stats: &FileStats,
        _structures: &[StructureInfo],
    ) -> QualityMetrics {
        let code_health_score = self.calculate_code_health_score(functions, file_stats);
        let maintainability_index = self.calculate_maintainability_index(functions, file_stats);
        let documentation_coverage = self.calculate_documentation_coverage(file_stats);
        let avg_complexity = self.calculate_average_complexity(functions);
        let function_size_health = self.calculate_function_size_health(functions, file_stats);
        let nesting_depth_health = self.calculate_nesting_depth_health(functions, file_stats);
        let code_duplication_ratio = self.estimate_code_duplication(file_stats);
        let technical_debt_ratio = self.calculate_technical_debt_ratio(functions, file_stats);

        QualityMetrics {
            code_health_score,
            maintainability_index,
            documentation_coverage,
            avg_complexity,
            function_size_health,
            nesting_depth_health,
            code_duplication_ratio,
            technical_debt_ratio,
        }
    }

    /// Calculate overall code health score based on practical metrics
    fn calculate_code_health_score(
        &self,
        functions: &[FunctionInfo],
        file_stats: &FileStats,
    ) -> f64 {
        let maintainability = self.calculate_maintainability_index(functions, file_stats);
        let documentation = self.calculate_documentation_coverage(file_stats);
        let complexity = 100.0 - (self.calculate_average_complexity(functions) * 10.0).min(100.0); // Invert complexity for score
        let function_size = self.calculate_function_size_health(functions, file_stats);
        let nesting_depth = self.calculate_nesting_depth_health(functions, file_stats);

        // Weighted average focusing on maintainability and complexity
        (maintainability * 0.3
            + documentation * 0.2
            + complexity * 0.25
            + function_size * 0.15
            + nesting_depth * 0.1)
            .clamp(0.0, 100.0)
    }

    /// Calculate industry-standard maintainability index
    fn calculate_maintainability_index(
        &self,
        functions: &[FunctionInfo],
        file_stats: &FileStats,
    ) -> f64 {
        // If no functions detected, estimate based on file characteristics
        if functions.is_empty() {
            let mut score = 85.0; // Start with good baseline

            // Apply progressive file length penalty
            let file_length_penalty = if file_stats.total_lines > 500 {
                if file_stats.total_lines > 2000 {
                    // Very large files: significant penalty
                    ((file_stats.total_lines - 500) as f64 / 100.0).min(40.0)
                } else if file_stats.total_lines > 1000 {
                    // Large files: moderate penalty
                    ((file_stats.total_lines - 500) as f64 / 150.0).min(25.0)
                } else {
                    // Medium files: small penalty
                    ((file_stats.total_lines - 500) as f64 / 200.0).min(15.0)
                }
            } else {
                0.0
            };

            score -= file_length_penalty;

            // Reward good documentation
            let doc_ratio = (file_stats.comment_lines + file_stats.doc_lines) as f64
                / file_stats.code_lines.max(1) as f64;
            if doc_ratio > 0.2 {
                score += 10.0;
            } else if doc_ratio < 0.05 {
                score -= 15.0;
            }

            // Penalize files with very little code (likely config files)
            if file_stats.code_lines < 10 {
                score -= 20.0;
            }

            return score.clamp(0.0, 100.0);
        }

        let mut total_score = 0.0;

        for func in functions {
            // Simplified maintainability calculation based on:
            // - Function length (shorter is better)
            // - Cyclomatic complexity (lower is better)
            // - Cognitive complexity (lower is better)
            // - Parameter count (fewer is better)

            let length_score = (50.0 - func.line_count as f64).max(0.0);
            let cyclomatic_score = (30.0 - func.cyclomatic_complexity as f64 * 2.0).max(0.0);
            let cognitive_score = (30.0 - func.cognitive_complexity as f64 * 2.0).max(0.0);
            let param_score = (20.0 - func.parameter_count as f64 * 3.0).max(0.0);

            total_score += length_score + cyclomatic_score + cognitive_score + param_score;
        }

        let base_score = (total_score / functions.len() as f64).clamp(0.0, 100.0);

        // Apply file length penalty - files over 500 lines are considered less maintainable
        let file_length_penalty = if file_stats.total_lines > 500 {
            if file_stats.total_lines > 2000 {
                // Very large files: significant penalty
                ((file_stats.total_lines - 500) as f64 / 100.0).min(40.0)
            } else if file_stats.total_lines > 1000 {
                // Large files: moderate penalty
                ((file_stats.total_lines - 500) as f64 / 150.0).min(25.0)
            } else {
                // Medium files: small penalty
                ((file_stats.total_lines - 500) as f64 / 200.0).min(15.0)
            }
        } else {
            0.0
        };

        (base_score - file_length_penalty).max(0.0)
    }

    /// Estimate code duplication ratio
    fn estimate_code_duplication(&self, file_stats: &FileStats) -> f64 {
        // More realistic estimation based on file characteristics
        let mut duplication_score: f64 = 0.0;

        // Base duplication estimate based on file size
        if file_stats.total_lines > 2000 {
            duplication_score += 12.0; // Large files tend to have more duplication
        } else if file_stats.total_lines > 1000 {
            duplication_score += 8.0;
        } else if file_stats.total_lines > 500 {
            duplication_score += 5.0;
        } else {
            duplication_score += 2.0; // Small files have minimal duplication
        }

        // Adjust based on code density
        let code_density = file_stats.code_lines as f64 / file_stats.total_lines.max(1) as f64;
        if code_density > 0.8 {
            duplication_score += 3.0; // Dense code files may have more duplication
        }

        // Adjust based on comment ratio (well-documented code tends to have less duplication)
        let comment_ratio = (file_stats.comment_lines + file_stats.doc_lines) as f64
            / file_stats.code_lines.max(1) as f64;
        if comment_ratio > 0.2 {
            duplication_score -= 2.0;
        } else if comment_ratio < 0.05 {
            duplication_score += 2.0;
        }

        duplication_score.clamp(0.0, 25.0) // Cap at 25% max duplication
    }

    /// Calculate code health metrics for the entire project
    pub fn calculate_project_quality_metrics(
        &self,
        functions: &[FunctionInfo],
        code_stats: &CodeStats,
        _structures: &[StructureInfo],
    ) -> QualityMetrics {
        // Create a synthetic FileStats for project-level calculations
        let project_file_stats = FileStats {
            total_lines: code_stats.total_lines,
            code_lines: code_stats.total_code_lines,
            comment_lines: code_stats.total_comment_lines,
            doc_lines: code_stats.total_doc_lines,
            blank_lines: code_stats.total_blank_lines,
            file_size: code_stats.total_size,
        };

        let code_health_score = self.calculate_code_health_score(functions, &project_file_stats);
        let maintainability_index =
            self.calculate_maintainability_index(functions, &project_file_stats);
        let documentation_coverage = self.calculate_documentation_coverage(&project_file_stats);
        let avg_complexity = self.calculate_average_complexity(functions);
        let function_size_health =
            self.calculate_function_size_health(functions, &project_file_stats);
        let nesting_depth_health =
            self.calculate_nesting_depth_health(functions, &project_file_stats);
        let code_duplication_ratio = self.estimate_project_code_duplication(code_stats);
        let technical_debt_ratio =
            self.calculate_technical_debt_ratio(functions, &project_file_stats);

        QualityMetrics {
            code_health_score,
            maintainability_index,
            documentation_coverage,
            avg_complexity,
            function_size_health,
            nesting_depth_health,
            code_duplication_ratio,
            technical_debt_ratio,
        }
    }

    /// Estimate code duplication for the entire project
    fn estimate_project_code_duplication(&self, code_stats: &CodeStats) -> f64 {
        let total_lines = code_stats.total_lines;
        let ratio = if total_lines > 10000 {
            0.20 // Assume 20% duplication in very large projects
        } else if total_lines > 5000 {
            0.15 // Assume 15% duplication in large projects
        } else if total_lines > 1000 {
            0.10 // Assume 10% duplication in medium projects
        } else {
            0.05 // Assume 5% duplication in small projects
        };

        ratio * 100.0 // Return as percentage
    }

    /// Classify complexity level based on cyclomatic complexity
    pub fn classify_complexity_level(&self, complexity: usize) -> ComplexityLevel {
        match complexity {
            1..=5 => ComplexityLevel::VeryLow,
            6..=10 => ComplexityLevel::Low,
            11..=20 => ComplexityLevel::Medium,
            21..=50 => ComplexityLevel::High,
            _ => ComplexityLevel::VeryHigh,
        }
    }

    /// Identify maintainability concerns for a function
    pub fn identify_maintainability_concerns(&self, func: &FunctionInfo) -> Vec<String> {
        let mut concerns = Vec::new();

        if func.line_count > 50 {
            concerns.push("Function is too long (>50 lines)".to_string());
        }

        if func.cyclomatic_complexity > 10 {
            concerns.push("High cyclomatic complexity".to_string());
        }

        if func.cognitive_complexity > 15 {
            concerns.push("High cognitive complexity".to_string());
        }

        if func.parameter_count > 5 {
            concerns.push("Too many parameters".to_string());
        }

        if func.nesting_depth > 4 {
            concerns.push("Deep nesting detected".to_string());
        }

        if func.has_recursion {
            concerns.push("Contains recursion".to_string());
        }

        if func.return_path_count > 5 {
            concerns.push("Multiple return paths".to_string());
        }

        concerns
    }

    /// Create detailed complexity information for functions
    pub fn create_function_complexity_details(
        &self,
        functions: &[FunctionInfo],
        file_path: &str,
    ) -> Vec<FunctionComplexityDetail> {
        functions
            .iter()
            .map(|func| {
                let complexity_level = self.classify_complexity_level(func.cyclomatic_complexity);
                let maintainability_concerns = self.identify_maintainability_concerns(func);

                FunctionComplexityDetail {
                    name: func.name.clone(),
                    file_path: file_path.to_string(),
                    start_line: func.start_line,
                    end_line: func.end_line,
                    line_count: func.line_count,
                    cyclomatic_complexity: func.cyclomatic_complexity,
                    cognitive_complexity: func.cognitive_complexity,
                    parameter_count: func.parameter_count,
                    return_path_count: func.return_path_count,
                    nesting_depth: func.nesting_depth,
                    is_method: func.is_method,
                    parent_class: func.parent_class.clone(),
                    local_variable_count: 0, // Placeholder, needs actual analysis
                    has_recursion: func.has_recursion,
                    has_exception_handling: func.has_exception_handling,
                    complexity_level,
                    maintainability_concerns,
                }
            })
            .collect()
    }

    /// Calculate documentation coverage percentage
    fn calculate_documentation_coverage(&self, file_stats: &FileStats) -> f64 {
        if file_stats.code_lines == 0 {
            return 0.0;
        }

        let documentation_lines = file_stats.comment_lines + file_stats.doc_lines;
        let coverage = (documentation_lines as f64 / file_stats.code_lines as f64) * 100.0;

        // More realistic documentation coverage scoring
        // 20% documentation coverage = 100 score (excellent)
        // 10% documentation coverage = 50 score (good)
        // 5% documentation coverage = 25 score (poor)
        // 0% documentation coverage = 0 score (very poor)
        (coverage * 5.0).min(100.0)
    }

    /// Calculate average cyclomatic complexity
    fn calculate_average_complexity(&self, functions: &[FunctionInfo]) -> f64 {
        if functions.is_empty() {
            return 0.0;
        }

        let total_complexity: usize = functions.iter().map(|f| f.cyclomatic_complexity).sum();
        total_complexity as f64 / functions.len() as f64
    }

    /// Calculate function size health score
    fn calculate_function_size_health(
        &self,
        functions: &[FunctionInfo],
        file_stats: &FileStats,
    ) -> f64 {
        // If no functions detected, estimate based on file characteristics
        if functions.is_empty() {
            let mut score = 75.0; // Start with decent baseline

            // Estimate based on lines of code per "logical unit"
            let avg_lines_per_unit =
                file_stats.code_lines as f64 / (file_stats.code_lines / 20).max(1) as f64;

            if avg_lines_per_unit > 50.0 {
                score -= (avg_lines_per_unit - 50.0) * 0.5;
            } else if avg_lines_per_unit < 5.0 {
                score -= (5.0 - avg_lines_per_unit) * 2.0;
            }

            // Penalize very large files
            if file_stats.total_lines > 500 {
                score -= ((file_stats.total_lines - 500) as f64 / 100.0).min(25.0);
            }

            return score.clamp(0.0, 100.0);
        }

        let mut score = 100.0;
        let avg_length =
            functions.iter().map(|f| f.line_count).sum::<usize>() as f64 / functions.len() as f64;

        // Penalty for functions that are too long
        if avg_length > 20.0 {
            score -= (avg_length - 20.0) * 2.0;
        }

        // Additional penalty for any extremely long functions
        for func in functions {
            if func.line_count > 100 {
                score -= 10.0;
            } else if func.line_count > 50 {
                score -= 5.0;
            }
        }

        score.clamp(0.0, 100.0)
    }

    /// Calculate nesting depth health score
    fn calculate_nesting_depth_health(
        &self,
        functions: &[FunctionInfo],
        file_stats: &FileStats,
    ) -> f64 {
        // If no functions detected, estimate based on file characteristics
        if functions.is_empty() {
            let mut score: f64 = 80.0; // Start with good baseline

            // Estimate nesting based on brace density
            let brace_density =
                file_stats.code_lines as f64 / (file_stats.total_lines.max(1) as f64);

            if brace_density > 0.8 {
                score -= 20.0; // Likely highly nested
            } else if brace_density > 0.6 {
                score -= 10.0; // Moderately nested
            }

            // Very large files tend to have more nesting
            if file_stats.total_lines > 1000 {
                score -= 15.0;
            }

            return score.clamp(0.0, 100.0);
        }

        let mut score = 100.0;
        let avg_nesting = functions.iter().map(|f| f.nesting_depth).sum::<usize>() as f64
            / functions.len() as f64;

        // Penalty for deep nesting
        if avg_nesting > 3.0 {
            score -= (avg_nesting - 3.0) * 15.0;
        }

        // Additional penalty for extremely nested functions
        for func in functions {
            if func.nesting_depth > 8 {
                score -= 15.0;
            } else if func.nesting_depth > 5 {
                score -= 10.0;
            }
        }

        score.clamp(0.0, 100.0)
    }

    /// Calculate technical debt ratio
    fn calculate_technical_debt_ratio(
        &self,
        functions: &[FunctionInfo],
        file_stats: &FileStats,
    ) -> f64 {
        if functions.is_empty() {
            return 0.0;
        }

        let mut debt_score = 0.0;

        // High complexity functions contribute to technical debt
        for func in functions {
            if func.cyclomatic_complexity > 20 {
                debt_score += 20.0;
            } else if func.cyclomatic_complexity > 10 {
                debt_score += 10.0;
            } else if func.cyclomatic_complexity > 5 {
                debt_score += 5.0;
            }
        }

        // Long functions contribute to technical debt
        for func in functions {
            if func.line_count > 100 {
                debt_score += 15.0;
            } else if func.line_count > 50 {
                debt_score += 10.0;
            }
        }

        // Lack of documentation contributes to technical debt
        let doc_coverage = self.calculate_documentation_coverage(file_stats);
        if doc_coverage < 20.0 {
            debt_score += 30.0 - doc_coverage;
        }

        // High nesting depth contributes to technical debt
        for func in functions {
            if func.nesting_depth > 5 {
                debt_score += (func.nesting_depth - 5) as f64 * 5.0;
            }
        }

        // Normalize to 0-100 scale
        let max_possible_debt = functions.len() as f64 * 50.0; // Rough estimate
        (debt_score / max_possible_debt.max(1.0) * 100.0).min(100.0)
    }
}

impl Default for QualityCalculator {
    fn default() -> Self {
        Self::new()
    }
}