1use crate::core::config::{validate_unit_range, ValknutConfig};
7use crate::core::errors::{Result, ValknutError};
8use serde::{Deserialize, Serialize};
9use std::path::PathBuf;
10
11#[derive(Debug, Clone, Serialize, Deserialize)]
16pub struct AnalysisConfig {
17 pub modules: AnalysisModules,
19
20 pub languages: LanguageSettings,
22
23 pub files: FileSettings,
25
26 pub quality: QualitySettings,
28
29 pub coverage: CoverageSettings,
31}
32
33#[derive(Debug, Clone, Serialize, Deserialize)]
35pub struct AnalysisModules {
36 pub complexity: bool,
38
39 pub dependencies: bool,
41
42 pub duplicates: bool,
44
45 pub refactoring: bool,
47
48 pub structure: bool,
50
51 pub coverage: bool,
53}
54
55#[derive(Debug, Clone, Serialize, Deserialize)]
57pub struct LanguageSettings {
58 pub enabled: Vec<String>,
60
61 pub max_file_size_mb: Option<f64>,
63
64 pub complexity_thresholds: std::collections::HashMap<String, f64>,
66}
67
68#[derive(Debug, Clone, Serialize, Deserialize)]
70pub struct FileSettings {
71 pub include_patterns: Vec<String>,
73
74 pub exclude_patterns: Vec<String>,
76
77 pub max_files: Option<usize>,
79
80 pub max_file_size_bytes: Option<u64>,
83
84 pub follow_symlinks: bool,
86}
87
88#[derive(Debug, Clone, Serialize, Deserialize)]
90pub struct QualitySettings {
91 pub confidence_threshold: f64,
93
94 pub max_analysis_time_per_file: Option<u64>,
96
97 pub strict_mode: bool,
99}
100
101#[derive(Debug, Clone, Serialize, Deserialize)]
103pub struct CoverageSettings {
104 pub enabled: bool,
106
107 pub file_path: Option<PathBuf>,
109
110 pub auto_discover: bool,
112
113 pub max_age_days: u32,
115
116 pub search_paths: Vec<String>,
118}
119
120impl Default for AnalysisConfig {
122 fn default() -> Self {
124 Self {
125 modules: AnalysisModules::default(),
126 languages: LanguageSettings::default(),
127 files: FileSettings::default(),
128 quality: QualitySettings::default(),
129 coverage: CoverageSettings::default(),
130 }
131 }
132}
133
134impl Default for AnalysisModules {
136 fn default() -> Self {
138 Self {
139 complexity: true,
140 dependencies: true,
141 duplicates: false,
142 refactoring: true,
143 structure: true,
144 coverage: true,
145 }
146 }
147}
148
149impl Default for LanguageSettings {
151 fn default() -> Self {
153 Self {
154 enabled: vec![
155 "python".to_string(),
156 "javascript".to_string(),
157 "typescript".to_string(),
158 ],
159 max_file_size_mb: Some(10.0),
160 complexity_thresholds: [
161 ("python".to_string(), 10.0),
162 ("javascript".to_string(), 10.0),
163 ("typescript".to_string(), 10.0),
164 ("rust".to_string(), 15.0),
165 ("go".to_string(), 12.0),
166 ]
167 .iter()
168 .cloned()
169 .collect(),
170 }
171 }
172}
173
174impl Default for FileSettings {
176 fn default() -> Self {
178 Self {
179 include_patterns: vec!["**/*".to_string()],
180 exclude_patterns: vec![
181 "*/node_modules/*".to_string(),
182 "*/venv/*".to_string(),
183 "*/target/*".to_string(),
184 "*/__pycache__/*".to_string(),
185 "*.min.js".to_string(),
186 ],
187 max_files: None,
188 max_file_size_bytes: Some(500 * 1024), follow_symlinks: false,
190 }
191 }
192}
193
194impl Default for QualitySettings {
196 fn default() -> Self {
198 Self {
199 confidence_threshold: 0.7,
200 max_analysis_time_per_file: Some(30),
201 strict_mode: false,
202 }
203 }
204}
205
206impl Default for CoverageSettings {
208 fn default() -> Self {
210 Self {
211 enabled: true,
212 file_path: None,
213 auto_discover: true,
214 max_age_days: 7,
215 search_paths: vec![
216 "./coverage/".to_string(),
217 "./target/coverage/".to_string(),
218 "./target/tarpaulin/".to_string(),
219 ],
220 }
221 }
222}
223
224impl AnalysisConfig {
226 pub fn new() -> Self {
228 Self::default()
229 }
230
231 pub fn modules(mut self, f: impl FnOnce(AnalysisModules) -> AnalysisModules) -> Self {
233 self.modules = f(self.modules);
234 self
235 }
236
237 pub fn languages(mut self, f: impl FnOnce(LanguageSettings) -> LanguageSettings) -> Self {
239 self.languages = f(self.languages);
240 self
241 }
242
243 pub fn files(mut self, f: impl FnOnce(FileSettings) -> FileSettings) -> Self {
245 self.files = f(self.files);
246 self
247 }
248
249 pub fn quality(mut self, f: impl FnOnce(QualitySettings) -> QualitySettings) -> Self {
251 self.quality = f(self.quality);
252 self
253 }
254
255 pub fn coverage(mut self, f: impl FnOnce(CoverageSettings) -> CoverageSettings) -> Self {
257 self.coverage = f(self.coverage);
258 self
259 }
260
261 pub fn with_languages(mut self, languages: Vec<String>) -> Self {
265 self.languages.enabled = languages;
266 self
267 }
268
269 pub fn with_language(mut self, language: impl Into<String>) -> Self {
271 self.languages.enabled.push(language.into());
272 self
273 }
274
275 pub fn with_confidence_threshold(mut self, threshold: f64) -> Self {
277 self.quality.confidence_threshold = threshold;
278 self
279 }
280
281 pub fn with_max_files(mut self, max_files: usize) -> Self {
283 self.files.max_files = Some(max_files);
284 self
285 }
286
287 pub fn exclude_pattern(mut self, pattern: impl Into<String>) -> Self {
289 self.files.exclude_patterns.push(pattern.into());
290 self
291 }
292
293 pub fn include_pattern(mut self, pattern: impl Into<String>) -> Self {
295 self.files.include_patterns.push(pattern.into());
296 self
297 }
298
299 pub fn enable_all_modules(mut self) -> Self {
301 self.modules.complexity = true;
302 self.modules.dependencies = true;
303 self.modules.duplicates = true;
304 self.modules.refactoring = true;
305 self.modules.structure = true;
306 self.modules.coverage = true;
307 self
308 }
309
310 pub fn disable_all_modules(mut self) -> Self {
312 self.modules.complexity = false;
313 self.modules.dependencies = false;
314 self.modules.duplicates = false;
315 self.modules.refactoring = false;
316 self.modules.structure = false;
317 self.modules.coverage = false;
318 self
319 }
320
321 pub fn essential_modules_only(mut self) -> Self {
323 self.modules.complexity = true;
324 self.modules.dependencies = false;
325 self.modules.duplicates = false;
326 self.modules.refactoring = false;
327 self.modules.structure = false;
328 self.modules.coverage = false;
329 self
330 }
331
332 pub fn validate(&self) -> Result<()> {
334 validate_unit_range(self.quality.confidence_threshold, "confidence_threshold")?;
336
337 if let Some(max_files) = self.files.max_files {
339 if max_files == 0 {
340 return Err(ValknutError::validation(
341 "max_files must be greater than 0 when specified",
342 ));
343 }
344 }
345
346 if let Some(max_size) = self.languages.max_file_size_mb {
348 if max_size <= 0.0 {
349 return Err(ValknutError::validation(
350 "max_file_size_mb must be positive when specified",
351 ));
352 }
353 }
354
355 if self.coverage.enabled && self.coverage.max_age_days == 0 && self.coverage.auto_discover {
357 }
359
360 let modules_enabled = [
362 self.modules.complexity,
363 self.modules.dependencies,
364 self.modules.duplicates,
365 self.modules.refactoring,
366 self.modules.structure,
367 self.modules.coverage,
368 ];
369 if !modules_enabled.iter().any(|&enabled| enabled) {
370 return Err(ValknutError::validation(
371 "At least one analysis module must be enabled",
372 ));
373 }
374
375 Ok(())
376 }
377
378 pub fn to_valknut_config(self) -> ValknutConfig {
383 let mut config = ValknutConfig::default();
384
385 config.analysis.enable_scoring = self.modules.complexity;
387 config.analysis.enable_graph_analysis = self.modules.dependencies;
388 config.analysis.enable_lsh_analysis = self.modules.duplicates;
389 config.analysis.enable_refactoring_analysis = self.modules.refactoring;
390 config.analysis.enable_structure_analysis = self.modules.structure;
391 config.analysis.enable_coverage_analysis = self.modules.coverage;
392
393 config.analysis.confidence_threshold = self.quality.confidence_threshold;
395
396 config.analysis.max_files = self.files.max_files.unwrap_or(0);
398 config.analysis.exclude_patterns = self.files.exclude_patterns;
399 config.analysis.include_patterns = self.files.include_patterns;
400
401 config.coverage.coverage_file = self.coverage.file_path;
403 config.coverage.auto_discover = self.coverage.auto_discover;
404 config.coverage.max_age_days = self.coverage.max_age_days;
405 config.coverage.search_paths = self.coverage.search_paths;
406
407 for language in &self.languages.enabled {
409 if let Some(lang_config) = config.languages.get_mut(language) {
410 lang_config.enabled = true;
411
412 if let Some(max_size) = self.languages.max_file_size_mb {
414 lang_config.max_file_size_mb = max_size;
415 }
416
417 if let Some(&threshold) = self.languages.complexity_thresholds.get(language) {
418 lang_config.complexity_threshold = threshold;
419 }
420 }
421 }
422
423 if let Some(timeout) = self.quality.max_analysis_time_per_file {
425 config.performance.file_timeout_seconds = timeout;
426 }
427
428 config
429 }
430
431 pub fn from_valknut_config(valknut_config: ValknutConfig) -> Result<Self> {
436 let enabled_languages: Vec<String> = valknut_config
438 .languages
439 .iter()
440 .filter_map(|(name, config)| {
441 if config.enabled {
442 Some(name.clone())
443 } else {
444 None
445 }
446 })
447 .collect();
448
449 let complexity_thresholds: std::collections::HashMap<String, f64> = valknut_config
451 .languages
452 .iter()
453 .filter(|(_, config)| config.enabled)
454 .map(|(name, config)| (name.clone(), config.complexity_threshold))
455 .collect();
456
457 let max_file_size_mb = valknut_config
459 .languages
460 .values()
461 .find(|config| config.enabled)
462 .map(|config| config.max_file_size_mb);
463
464 Ok(Self {
465 modules: AnalysisModules {
466 complexity: valknut_config.analysis.enable_scoring,
467 dependencies: valknut_config.analysis.enable_graph_analysis,
468 duplicates: valknut_config.analysis.enable_lsh_analysis,
469 refactoring: valknut_config.analysis.enable_refactoring_analysis,
470 structure: valknut_config.analysis.enable_structure_analysis,
471 coverage: valknut_config.analysis.enable_coverage_analysis,
472 },
473 languages: LanguageSettings {
474 enabled: enabled_languages,
475 max_file_size_mb,
476 complexity_thresholds,
477 },
478 files: FileSettings {
479 include_patterns: valknut_config.analysis.include_patterns,
480 exclude_patterns: valknut_config.analysis.exclude_patterns,
481 max_files: if valknut_config.analysis.max_files == 0 {
482 None
483 } else {
484 Some(valknut_config.analysis.max_files)
485 },
486 max_file_size_bytes: if valknut_config.analysis.max_file_size_bytes == 0 {
487 None
488 } else {
489 Some(valknut_config.analysis.max_file_size_bytes)
490 },
491 follow_symlinks: false, },
493 quality: QualitySettings {
494 confidence_threshold: valknut_config.analysis.confidence_threshold,
495 max_analysis_time_per_file: Some(valknut_config.performance.file_timeout_seconds),
496 strict_mode: false, },
498 coverage: CoverageSettings {
499 enabled: valknut_config.analysis.enable_coverage_analysis,
500 file_path: valknut_config.coverage.coverage_file,
501 auto_discover: valknut_config.coverage.auto_discover,
502 max_age_days: valknut_config.coverage.max_age_days,
503 search_paths: valknut_config.coverage.search_paths,
504 },
505 })
506 }
507}
508
509impl AnalysisModules {
513 pub fn all() -> Self {
515 Self {
516 complexity: true,
517 dependencies: true,
518 duplicates: true,
519 refactoring: true,
520 structure: true,
521 coverage: true,
522 }
523 }
524
525 pub fn essential() -> Self {
530 Self {
531 complexity: true,
532 dependencies: false,
533 duplicates: false,
534 refactoring: false,
535 structure: false,
536 coverage: false,
537 }
538 }
539
540 pub fn code_quality() -> Self {
544 Self {
545 complexity: true,
546 dependencies: false,
547 duplicates: true,
548 refactoring: true,
549 structure: false,
550 coverage: false,
551 }
552 }
553}
554
555impl LanguageSettings {
557 pub fn add_language(mut self, language: impl Into<String>) -> Self {
559 self.enabled.push(language.into());
560 self
561 }
562
563 pub fn with_complexity_threshold(
565 mut self,
566 language: impl Into<String>,
567 threshold: f64,
568 ) -> Self {
569 self.complexity_thresholds
570 .insert(language.into(), threshold);
571 self
572 }
573
574 pub fn with_max_file_size_mb(mut self, size_mb: f64) -> Self {
576 self.max_file_size_mb = Some(size_mb);
577 self
578 }
579}
580
581impl FileSettings {
583 pub fn exclude_patterns(mut self, patterns: Vec<String>) -> Self {
585 self.exclude_patterns.extend(patterns);
586 self
587 }
588
589 pub fn include_patterns(mut self, patterns: Vec<String>) -> Self {
591 self.include_patterns.extend(patterns);
592 self
593 }
594
595 pub fn with_max_files(mut self, max_files: usize) -> Self {
597 self.max_files = Some(max_files);
598 self
599 }
600}
601
602impl QualitySettings {
604 pub fn strict(mut self) -> Self {
606 self.strict_mode = true;
607 self
608 }
609
610 pub fn with_timeout(mut self, seconds: u64) -> Self {
612 self.max_analysis_time_per_file = Some(seconds);
613 self
614 }
615}
616
617impl CoverageSettings {
619 pub fn disabled() -> Self {
621 Self {
622 enabled: false,
623 ..Self::default()
624 }
625 }
626
627 pub fn with_file(mut self, path: PathBuf) -> Self {
629 self.file_path = Some(path);
630 self.auto_discover = false;
631 self
632 }
633
634 pub fn with_search_paths(mut self, paths: Vec<String>) -> Self {
636 self.search_paths.extend(paths);
637 self
638 }
639}
640
641
642#[cfg(test)]
643#[path = "config_types_tests.rs"]
644mod tests;