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 item_dependencies: Vec<ItemDependency>,
279 pub is_test_module: bool,
281 pub test_function_count: usize,
283 pub subdomain: Option<Subdomain>,
285}
286
287impl ModuleMetrics {
288 pub fn new(path: PathBuf, name: String) -> Self {
290 Self {
291 path,
292 name,
293 ..Default::default()
294 }
295 }
296
297 pub fn add_type_definition(&mut self, name: String, visibility: Visibility, is_trait: bool) {
299 self.type_definitions.insert(
300 name.clone(),
301 TypeDefinition {
302 name,
303 visibility,
304 is_trait,
305 is_newtype: false,
306 inner_type: None,
307 has_serde_derive: false,
308 public_field_count: 0,
309 total_field_count: 0,
310 },
311 );
312 }
313
314 #[allow(clippy::too_many_arguments)]
316 pub fn add_type_definition_full(
317 &mut self,
318 name: String,
319 visibility: Visibility,
320 is_trait: bool,
321 is_newtype: bool,
322 inner_type: Option<String>,
323 has_serde_derive: bool,
324 public_field_count: usize,
325 total_field_count: usize,
326 ) {
327 self.type_definitions.insert(
328 name.clone(),
329 TypeDefinition {
330 name,
331 visibility,
332 is_trait,
333 is_newtype,
334 inner_type,
335 has_serde_derive,
336 public_field_count,
337 total_field_count,
338 },
339 );
340 }
341
342 pub fn add_function_definition(&mut self, name: String, visibility: Visibility) {
344 self.function_definitions.insert(
345 name.clone(),
346 FunctionDefinition {
347 name,
348 visibility,
349 param_count: 0,
350 primitive_param_count: 0,
351 param_types: Vec::new(),
352 },
353 );
354 }
355
356 pub fn add_function_definition_full(
358 &mut self,
359 name: String,
360 visibility: Visibility,
361 param_count: usize,
362 primitive_param_count: usize,
363 param_types: Vec<String>,
364 ) {
365 self.function_definitions.insert(
366 name.clone(),
367 FunctionDefinition {
368 name,
369 visibility,
370 param_count,
371 primitive_param_count,
372 param_types,
373 },
374 );
375 }
376
377 pub fn get_type_visibility(&self, name: &str) -> Option<Visibility> {
379 self.type_definitions.get(name).map(|t| t.visibility)
380 }
381
382 pub fn public_type_count(&self) -> usize {
384 self.type_definitions
385 .values()
386 .filter(|t| t.visibility == Visibility::Public)
387 .count()
388 }
389
390 pub fn private_type_count(&self) -> usize {
392 self.type_definitions
393 .values()
394 .filter(|t| t.visibility != Visibility::Public)
395 .count()
396 }
397
398 pub fn average_strength(&self) -> f64 {
400 let total = self.trait_impl_count + self.inherent_impl_count;
401 if total == 0 {
402 return 0.0;
403 }
404
405 let contract_weight = self.trait_impl_count as f64 * IntegrationStrength::Contract.value();
406 let intrusive_weight =
407 self.inherent_impl_count as f64 * IntegrationStrength::Intrusive.value();
408
409 (contract_weight + intrusive_weight) / total as f64
410 }
411
412 pub fn newtype_count(&self) -> usize {
414 self.type_definitions
415 .values()
416 .filter(|t| t.is_newtype)
417 .count()
418 }
419
420 pub fn serde_type_count(&self) -> usize {
422 self.type_definitions
423 .values()
424 .filter(|t| t.has_serde_derive)
425 .count()
426 }
427
428 pub fn newtype_ratio(&self) -> f64 {
430 let non_trait_types = self
431 .type_definitions
432 .values()
433 .filter(|t| !t.is_trait)
434 .count();
435 if non_trait_types == 0 {
436 return 0.0;
437 }
438 self.newtype_count() as f64 / non_trait_types as f64
439 }
440
441 pub fn types_with_public_fields(&self) -> usize {
443 self.type_definitions
444 .values()
445 .filter(|t| t.public_field_count > 0)
446 .count()
447 }
448
449 pub fn function_count(&self) -> usize {
451 self.function_definitions.len()
452 }
453
454 pub fn functions_with_primitive_obsession(&self) -> Vec<&FunctionDefinition> {
457 self.function_definitions
458 .values()
459 .filter(|f| {
460 f.param_count >= 3 && f.primitive_param_count as f64 / f.param_count as f64 >= 0.6
461 })
462 .collect()
463 }
464
465 pub fn is_god_module(&self, max_functions: usize, max_types: usize, max_impls: usize) -> bool {
468 self.function_count() > max_functions
469 || self.type_definitions.len() > max_types
470 || (self.trait_impl_count + self.inherent_impl_count) > max_impls
471 }
472}
473
474