1use std::collections::HashMap;
2use std::path::PathBuf;
3
4use crate::analyzer::ItemDependency;
5use crate::volatility::Volatility;
6
7use super::dimensions::{Distance, IntegrationStrength, Subdomain, Visibility};
8
9#[derive(Debug, Clone)]
10pub struct TypeDefinition {
11 pub name: String,
13 pub visibility: Visibility,
15 pub is_trait: bool,
17 pub is_newtype: bool,
19 pub inner_type: Option<String>,
21 pub has_serde_derive: bool,
23 pub public_field_count: usize,
25 pub total_field_count: usize,
27}
28
29#[derive(Debug, Clone)]
31pub struct FunctionDefinition {
32 pub name: String,
34 pub visibility: Visibility,
36 pub param_count: usize,
38 pub primitive_param_count: usize,
40 pub param_types: Vec<String>,
42}
43
44#[derive(Debug, Clone, Copy, PartialEq, Eq)]
46pub enum BalanceClassification {
47 HighCohesion,
49 LooseCoupling,
51 Acceptable,
53 Pain,
55 LocalComplexity,
57}
58
59impl BalanceClassification {
60 pub fn classify(
62 strength: IntegrationStrength,
63 distance: Distance,
64 volatility: Volatility,
65 ) -> Self {
66 let is_strong = strength.value() >= 0.5;
67 let is_far = distance.value() >= 0.5;
68 let is_volatile = volatility == Volatility::High;
69
70 match (is_strong, is_far, is_volatile) {
71 (true, false, _) => BalanceClassification::HighCohesion,
72 (false, true, _) => BalanceClassification::LooseCoupling,
73 (false, false, _) => BalanceClassification::LocalComplexity,
74 (true, true, false) => BalanceClassification::Acceptable,
75 (true, true, true) => BalanceClassification::Pain,
76 }
77 }
78
79 pub fn description_ja(&self) -> &'static str {
81 match self {
82 BalanceClassification::HighCohesion => "高凝集 (強+近)",
83 BalanceClassification::LooseCoupling => "疎結合 (弱+遠)",
84 BalanceClassification::Acceptable => "許容可能 (強+遠+安定)",
85 BalanceClassification::Pain => "要改善 (強+遠+変動)",
86 BalanceClassification::LocalComplexity => "局所複雑性 (弱+近)",
87 }
88 }
89
90 pub fn description_en(&self) -> &'static str {
92 match self {
93 BalanceClassification::HighCohesion => "High Cohesion",
94 BalanceClassification::LooseCoupling => "Loose Coupling",
95 BalanceClassification::Acceptable => "Acceptable",
96 BalanceClassification::Pain => "Needs Refactoring",
97 BalanceClassification::LocalComplexity => "Local Complexity",
98 }
99 }
100
101 pub fn is_ideal(&self) -> bool {
103 matches!(
104 self,
105 BalanceClassification::HighCohesion | BalanceClassification::LooseCoupling
106 )
107 }
108
109 pub fn needs_attention(&self) -> bool {
111 matches!(
112 self,
113 BalanceClassification::Pain | BalanceClassification::LocalComplexity
114 )
115 }
116}
117
118#[derive(Debug, Clone, Default)]
120pub struct DimensionStats {
121 pub strength_counts: StrengthCounts,
123 pub distance_counts: DistanceCounts,
125 pub volatility_counts: VolatilityCounts,
127 pub balance_counts: BalanceCounts,
129}
130
131impl DimensionStats {
132 pub fn total(&self) -> usize {
134 self.strength_counts.total()
135 }
136
137 pub fn strength_percentages(&self) -> (f64, f64, f64, f64) {
139 let total = self.total() as f64;
140 if total == 0.0 {
141 return (0.0, 0.0, 0.0, 0.0);
142 }
143 (
144 self.strength_counts.intrusive as f64 / total * 100.0,
145 self.strength_counts.functional as f64 / total * 100.0,
146 self.strength_counts.model as f64 / total * 100.0,
147 self.strength_counts.contract as f64 / total * 100.0,
148 )
149 }
150
151 pub fn distance_percentages(&self) -> (f64, f64, f64) {
153 let total = self.total() as f64;
154 if total == 0.0 {
155 return (0.0, 0.0, 0.0);
156 }
157 (
158 self.distance_counts.same_module as f64 / total * 100.0,
159 self.distance_counts.different_module as f64 / total * 100.0,
160 self.distance_counts.different_crate as f64 / total * 100.0,
161 )
162 }
163
164 pub fn volatility_percentages(&self) -> (f64, f64, f64) {
166 let total = self.total() as f64;
167 if total == 0.0 {
168 return (0.0, 0.0, 0.0);
169 }
170 (
171 self.volatility_counts.low as f64 / total * 100.0,
172 self.volatility_counts.medium as f64 / total * 100.0,
173 self.volatility_counts.high as f64 / total * 100.0,
174 )
175 }
176
177 pub fn ideal_count(&self) -> usize {
179 self.balance_counts.high_cohesion + self.balance_counts.loose_coupling
180 }
181
182 pub fn problematic_count(&self) -> usize {
184 self.balance_counts.pain + self.balance_counts.local_complexity
185 }
186
187 pub fn ideal_percentage(&self) -> f64 {
189 let total = self.total() as f64;
190 if total == 0.0 {
191 return 0.0;
192 }
193 self.ideal_count() as f64 / total * 100.0
194 }
195}
196
197#[derive(Debug, Clone, Default)]
199pub struct StrengthCounts {
200 pub intrusive: usize,
202 pub functional: usize,
204 pub model: usize,
206 pub contract: usize,
208}
209
210impl StrengthCounts {
211 pub fn total(&self) -> usize {
213 self.intrusive + self.functional + self.model + self.contract
214 }
215}
216
217#[derive(Debug, Clone, Default)]
219pub struct DistanceCounts {
220 pub same_module: usize,
222 pub different_module: usize,
224 pub different_crate: usize,
226}
227
228#[derive(Debug, Clone, Default)]
230pub struct VolatilityCounts {
231 pub low: usize,
233 pub medium: usize,
235 pub high: usize,
237}
238
239#[derive(Debug, Clone, Default)]
241pub struct BalanceCounts {
242 pub high_cohesion: usize,
244 pub loose_coupling: usize,
246 pub acceptable: usize,
248 pub pain: usize,
250 pub local_complexity: usize,
252}
253
254#[derive(Debug, Clone, Default)]
256pub struct ModuleMetrics {
257 pub path: PathBuf,
259 pub name: String,
261 pub trait_impl_count: usize,
263 pub inherent_impl_count: usize,
265 pub function_call_count: usize,
267 pub type_usage_count: usize,
269 pub external_deps: Vec<String>,
271 pub internal_deps: Vec<String>,
273 pub type_definitions: HashMap<String, TypeDefinition>,
275 pub function_definitions: HashMap<String, FunctionDefinition>,
277 pub method_definitions: HashMap<String, Visibility>,
279 pub item_locations: HashMap<String, usize>,
281 pub item_dependencies: Vec<ItemDependency>,
283 pub is_test_module: bool,
285 pub test_function_count: usize,
287 pub subdomain: Option<Subdomain>,
289}
290
291impl ModuleMetrics {
292 pub fn new(path: PathBuf, name: String) -> Self {
294 Self {
295 path,
296 name,
297 ..Default::default()
298 }
299 }
300
301 pub fn add_type_definition(&mut self, name: String, visibility: Visibility, is_trait: bool) {
303 self.type_definitions.insert(
304 name.clone(),
305 TypeDefinition {
306 name,
307 visibility,
308 is_trait,
309 is_newtype: false,
310 inner_type: None,
311 has_serde_derive: false,
312 public_field_count: 0,
313 total_field_count: 0,
314 },
315 );
316 }
317
318 #[allow(clippy::too_many_arguments)]
320 pub fn add_type_definition_full(
321 &mut self,
322 name: String,
323 visibility: Visibility,
324 is_trait: bool,
325 is_newtype: bool,
326 inner_type: Option<String>,
327 has_serde_derive: bool,
328 public_field_count: usize,
329 total_field_count: usize,
330 ) {
331 self.type_definitions.insert(
332 name.clone(),
333 TypeDefinition {
334 name,
335 visibility,
336 is_trait,
337 is_newtype,
338 inner_type,
339 has_serde_derive,
340 public_field_count,
341 total_field_count,
342 },
343 );
344 }
345
346 pub fn add_function_definition(&mut self, name: String, visibility: Visibility) {
348 self.function_definitions.insert(
349 name.clone(),
350 FunctionDefinition {
351 name,
352 visibility,
353 param_count: 0,
354 primitive_param_count: 0,
355 param_types: Vec::new(),
356 },
357 );
358 }
359
360 pub fn add_function_definition_full(
362 &mut self,
363 name: String,
364 visibility: Visibility,
365 param_count: usize,
366 primitive_param_count: usize,
367 param_types: Vec<String>,
368 ) {
369 self.function_definitions.insert(
370 name.clone(),
371 FunctionDefinition {
372 name,
373 visibility,
374 param_count,
375 primitive_param_count,
376 param_types,
377 },
378 );
379 }
380
381 pub fn get_type_visibility(&self, name: &str) -> Option<Visibility> {
383 self.type_definitions.get(name).map(|t| t.visibility)
384 }
385
386 pub fn public_type_count(&self) -> usize {
388 self.type_definitions
389 .values()
390 .filter(|t| t.visibility == Visibility::Public)
391 .count()
392 }
393
394 pub fn private_type_count(&self) -> usize {
396 self.type_definitions
397 .values()
398 .filter(|t| t.visibility != Visibility::Public)
399 .count()
400 }
401
402 pub fn average_strength(&self) -> f64 {
404 let total = self.trait_impl_count + self.inherent_impl_count;
405 if total == 0 {
406 return 0.0;
407 }
408
409 let contract_weight = self.trait_impl_count as f64 * IntegrationStrength::Contract.value();
410 let intrusive_weight =
411 self.inherent_impl_count as f64 * IntegrationStrength::Intrusive.value();
412
413 (contract_weight + intrusive_weight) / total as f64
414 }
415
416 pub fn newtype_count(&self) -> usize {
418 self.type_definitions
419 .values()
420 .filter(|t| t.is_newtype)
421 .count()
422 }
423
424 pub fn serde_type_count(&self) -> usize {
426 self.type_definitions
427 .values()
428 .filter(|t| t.has_serde_derive)
429 .count()
430 }
431
432 pub fn newtype_ratio(&self) -> f64 {
434 let non_trait_types = self
435 .type_definitions
436 .values()
437 .filter(|t| !t.is_trait)
438 .count();
439 if non_trait_types == 0 {
440 return 0.0;
441 }
442 self.newtype_count() as f64 / non_trait_types as f64
443 }
444
445 pub fn types_with_public_fields(&self) -> usize {
447 self.type_definitions
448 .values()
449 .filter(|t| t.public_field_count > 0)
450 .count()
451 }
452
453 pub fn function_count(&self) -> usize {
455 self.function_definitions.len()
456 }
457
458 pub fn functions_with_primitive_obsession(&self) -> Vec<&FunctionDefinition> {
461 self.function_definitions
462 .values()
463 .filter(|f| {
464 f.param_count >= 3 && f.primitive_param_count as f64 / f.param_count as f64 >= 0.6
465 })
466 .collect()
467 }
468
469 pub fn is_god_module(&self, max_functions: usize, max_types: usize, max_impls: usize) -> bool {
472 self.function_count() > max_functions
473 || self.type_definitions.len() > max_types
474 || (self.trait_impl_count + self.inherent_impl_count) > max_impls
475 }
476}
477
478