1use crate::error::Result;
7use std::collections::HashMap;
8use std::fs;
9use std::path::{Path, PathBuf};
10
11#[derive(Debug)]
13#[allow(dead_code)]
14pub struct DocumentationAnalyzer {
15 config: AnalyzerConfig,
17 analysis_results: AnalysisResults,
19 metrics: DocumentationMetrics,
21}
22
23#[derive(Debug, Clone)]
25pub struct AnalyzerConfig {
26 pub source_directories: Vec<PathBuf>,
28 pub docs_output_dir: PathBuf,
30 pub min_coverage_threshold: f64,
32 pub verify_examples: bool,
34 pub check_links: bool,
36 pub check_style_consistency: bool,
38 pub required_sections: Vec<String>,
40 pub language_preferences: Vec<String>,
42}
43
44impl Default for AnalyzerConfig {
45 fn default() -> Self {
46 Self {
47 source_directories: vec![PathBuf::from("src")],
48 docs_output_dir: PathBuf::from("target/doc"),
49 min_coverage_threshold: 0.8, verify_examples: true,
51 check_links: true,
52 check_style_consistency: true,
53 required_sections: vec![
54 "Examples".to_string(),
55 "Arguments".to_string(),
56 "Returns".to_string(),
57 "Errors".to_string(),
58 ],
59 language_preferences: vec!["en".to_string()],
60 }
61 }
62}
63
64#[derive(Debug)]
66pub struct AnalysisResults {
67 pub coverage: CoverageAnalysis,
69 pub example_verification: ExampleVerificationResults,
71 pub link_checking: LinkCheckingResults,
73 pub style_analysis: StyleAnalysis,
75 pub api_completeness: ApiCompletenessAnalysis,
77 pub overall_quality_score: f64,
79}
80
81#[derive(Debug)]
83pub struct CoverageAnalysis {
84 pub total_public_items: usize,
86 pub documented_items: usize,
88 pub coverage_percentage: f64,
90 pub undocumented_by_category: HashMap<ItemCategory, Vec<UndocumentedItem>>,
92 pub quality_by_module: HashMap<String, f64>,
94}
95
96#[derive(Debug, Clone, PartialEq, Eq, Hash)]
98pub enum ItemCategory {
99 Function,
101 Struct,
103 Enum,
105 Trait,
107 Module,
109 Constant,
111 Macro,
113}
114
115#[derive(Debug, Clone)]
117pub struct UndocumentedItem {
118 pub name: String,
120 pub file_path: PathBuf,
122 pub line_number: usize,
124 pub category: ItemCategory,
126 pub visibility: VisibilityLevel,
128 pub suggested_template: String,
130}
131
132#[derive(Debug, Clone)]
134pub enum VisibilityLevel {
135 Public,
136 PublicCrate,
137 PublicSuper,
138 Private,
139}
140
141#[derive(Debug, Default)]
143pub struct ExampleVerificationResults {
144 pub total_examples: usize,
146 pub compiled_examples: usize,
148 pub failed_examples: Vec<FailedExample>,
150 pub example_coverage: HashMap<String, ExampleCoverage>,
152 pub quality_metrics: ExampleQualityMetrics,
154}
155
156#[derive(Debug, Clone)]
158pub struct FailedExample {
159 pub name: String,
161 pub file_path: PathBuf,
163 pub line_number: usize,
165 pub error_message: String,
167 pub source_code: String,
169 pub suggested_fix: Option<String>,
171}
172
173#[derive(Debug, Clone)]
175pub struct ExampleCoverage {
176 pub functions_with_examples: usize,
178 pub total_functions: usize,
180 pub coverage_percentage: f64,
182 pub complexity_distribution: HashMap<ExampleComplexity, usize>,
184}
185
186#[derive(Debug, Clone, PartialEq, Eq, Hash)]
188pub enum ExampleComplexity {
189 Basic,
191 Intermediate,
193 Advanced,
195 Integration,
197}
198
199#[derive(Debug, Clone)]
201pub struct ExampleQualityMetrics {
202 pub average_length: f64,
204 pub error_handling_coverage: f64,
206 pub comment_coverage: f64,
208 pub best_practices_score: f64,
210}
211
212#[derive(Debug)]
214pub struct LinkCheckingResults {
215 pub total_links: usize,
217 pub valid_links: usize,
219 pub broken_links: Vec<BrokenLink>,
221 pub external_link_status: HashMap<String, LinkStatus>,
223 pub internal_link_consistency: f64,
225}
226
227#[derive(Debug, Clone)]
229pub struct BrokenLink {
230 pub url: String,
232 pub source_file: PathBuf,
234 pub line_number: usize,
236 pub error_type: LinkErrorType,
238 pub error_message: String,
240 pub suggested_replacement: Option<String>,
242}
243
244#[derive(Debug, Clone)]
246pub enum LinkErrorType {
247 HttpError(u16),
249 Timeout,
251 InvalidFormat,
253 MissingReference,
255 CircularReference,
257}
258
259#[derive(Debug, Clone)]
261pub enum LinkStatus {
262 Valid,
263 Broken(String),
264 Redirected(String),
265 Timeout,
266}
267
268#[derive(Debug)]
270pub struct StyleAnalysis {
271 pub consistency_score: f64,
273 pub violations: HashMap<StyleCategory, Vec<StyleViolation>>,
275 pub recommendations: Vec<StyleRecommendation>,
277 pub format_analysis: FormatAnalysis,
279}
280
281#[derive(Debug, Clone, PartialEq, Eq, Hash)]
283pub enum StyleCategory {
284 HeadingStyle,
286 CodeFormatting,
288 ParameterStyle,
290 ErrorStyle,
292 ExampleStyle,
294 LanguageStyle,
296}
297
298#[derive(Debug, Clone)]
300pub struct StyleViolation {
301 pub file_path: PathBuf,
303 pub line_number: usize,
305 pub description: String,
307 pub current_content: String,
309 pub suggested_fix: String,
311 pub severity: ViolationSeverity,
313}
314
315#[derive(Debug, Clone)]
317pub enum ViolationSeverity {
318 Low,
319 Medium,
320 High,
321 Critical,
322}
323
324#[derive(Debug, Clone)]
326pub struct StyleRecommendation {
327 pub category: StyleCategory,
329 pub description: String,
331 pub implementation_steps: Vec<String>,
333 pub expected_impact: f64,
335}
336
337#[derive(Debug, Clone)]
339pub struct FormatAnalysis {
340 pub markdown_compliance: f64,
342 pub rustdoc_compliance: f64,
344 pub cross_reference_completeness: f64,
346 pub toc_quality: f64,
348}
349
350#[derive(Debug, Default)]
352pub struct ApiCompletenessAnalysis {
353 pub missing_sections: HashMap<String, Vec<MissingSection>>,
355 pub evolution_tracking: ApiEvolutionAnalysis,
357 pub documentation_debt: DocumentationDebt,
359 pub accessibility_compliance: AccessibilityAnalysis,
361}
362
363#[derive(Debug, Clone)]
365pub struct MissingSection {
366 pub section_name: String,
368 pub item_name: String,
370 pub file_path: PathBuf,
372 pub priority: Priority,
374 pub suggested_template: String,
376}
377
378#[derive(Debug, Clone)]
380pub enum Priority {
381 Low,
382 Medium,
383 High,
384 Critical,
385}
386
387#[derive(Debug, Clone, Default)]
389pub struct ApiEvolutionAnalysis {
390 pub new_apis: Vec<String>,
392 pub deprecated_apis: Vec<String>,
394 pub changed_apis: Vec<ApiChange>,
396 pub breaking_changes: Vec<BreakingChange>,
398}
399
400#[derive(Debug, Clone)]
402pub struct ApiChange {
403 pub api_name: String,
405 pub change_type: ChangeType,
407 pub description: String,
409 pub documentation_updated: bool,
411}
412
413#[derive(Debug, Clone)]
415pub enum ChangeType {
416 SignatureChange,
418 BehaviorChange,
420 PerformanceChange,
422 ErrorHandlingChange,
424}
425
426#[derive(Debug, Clone)]
428pub struct BreakingChange {
429 pub api_name: String,
431 pub description: String,
433 pub migration_guide_available: bool,
435 pub migration_path: String,
437}
438
439#[derive(Debug, Clone)]
441pub struct DocumentationDebt {
442 pub total_debt_score: f64,
444 pub debt_by_category: HashMap<DebtCategory, f64>,
446 pub high_priority_items: Vec<DebtItem>,
448 pub estimated_effort_hours: f64,
450}
451
452#[derive(Debug, Clone, PartialEq, Eq, Hash)]
454pub enum DebtCategory {
455 MissingDocumentation,
457 OutdatedDocumentation,
459 PoorQuality,
461 MissingExamples,
463 BrokenReferences,
465}
466
467#[derive(Debug, Clone)]
469pub struct DebtItem {
470 pub category: DebtCategory,
472 pub description: String,
474 pub file_path: PathBuf,
476 pub priority: Priority,
478 pub estimated_effort: f64,
480}
481
482#[derive(Debug, Clone)]
484pub struct AccessibilityAnalysis {
485 pub alttext_coverage: f64,
487 pub color_contrast_compliance: f64,
489 pub screen_reader_compatibility: f64,
491 pub keyboard_navigation_support: f64,
493 pub overall_accessibility_score: f64,
495}
496
497#[derive(Debug)]
499pub struct DocumentationMetrics {
500 pub total_doc_lines: usize,
502 pub code_to_doc_ratio: f64,
504 pub average_quality_score: f64,
506 pub maintenance_burden: f64,
508 pub user_satisfaction: UserSatisfactionMetrics,
510}
511
512#[derive(Debug, Clone)]
514pub struct UserSatisfactionMetrics {
515 pub clarity_score: f64,
517 pub completeness_score: f64,
519 pub helpfulness_score: f64,
521 pub overall_satisfaction: f64,
523}
524
525impl DocumentationAnalyzer {
526 pub fn new(config: AnalyzerConfig) -> Self {
528 Self {
529 config,
530 analysis_results: AnalysisResults::default(),
531 metrics: DocumentationMetrics::default(),
532 }
533 }
534
535 pub fn analyze(&mut self) -> Result<&AnalysisResults> {
537 println!("Starting comprehensive documentation analysis...");
538
539 self.analyze_coverage()?;
541
542 if self.config.verify_examples {
544 self.verify_examples()?;
545 }
546
547 if self.config.check_links {
549 self.check_links()?;
550 }
551
552 if self.config.check_style_consistency {
554 self.analyze_style_consistency()?;
555 }
556
557 self.analyze_api_completeness()?;
559
560 self.calculate_overall_quality_score();
562
563 println!("Documentation analysis completed.");
564 Ok(&self.analysis_results)
565 }
566
567 fn analyze_coverage(&mut self) -> Result<()> {
569 println!("Analyzing documentation coverage...");
570
571 let mut total_items = 0;
572 let mut documented_items = 0;
573 let mut undocumented_by_category = HashMap::new();
574 let mut quality_by_module = HashMap::new();
575
576 for source_dir in &self.config.source_directories {
577 self.analyze_directory_coverage(
578 source_dir,
579 &mut total_items,
580 &mut documented_items,
581 &mut undocumented_by_category,
582 &mut quality_by_module,
583 )?;
584 }
585
586 let coverage_percentage = if total_items > 0 {
587 (documented_items as f64 / total_items as f64) * 100.0
588 } else {
589 100.0
590 };
591
592 self.analysis_results.coverage = CoverageAnalysis {
593 total_public_items: total_items,
594 documented_items,
595 coverage_percentage,
596 undocumented_by_category,
597 quality_by_module,
598 };
599
600 println!("Coverage analysis completed: {:.1}%", coverage_percentage);
601 Ok(())
602 }
603
604 fn analyze_directory_coverage(
606 &self,
607 dir: &Path,
608 total_items: &mut usize,
609 documented_items: &mut usize,
610 undocumented_by_category: &mut HashMap<ItemCategory, Vec<UndocumentedItem>>,
611 quality_by_module: &mut HashMap<String, f64>,
612 ) -> Result<()> {
613 if !dir.exists() {
614 return Ok(());
615 }
616
617 for entry in fs::read_dir(dir)? {
618 let entry = entry?;
619 let path = entry.path();
620
621 if path.is_dir() {
622 self.analyze_directory_coverage(
623 &path,
624 total_items,
625 documented_items,
626 undocumented_by_category,
627 quality_by_module,
628 )?;
629 } else if path.extension().and_then(|s| s.to_str()) == Some("rs") {
630 self.analyze_file_coverage(
631 &path,
632 total_items,
633 documented_items,
634 undocumented_by_category,
635 quality_by_module,
636 )?;
637 }
638 }
639
640 Ok(())
641 }
642
643 fn analyze_file_coverage(
645 &self,
646 file_path: &Path,
647 total_items: &mut usize,
648 documented_items: &mut usize,
649 undocumented_by_category: &mut HashMap<ItemCategory, Vec<UndocumentedItem>>,
650 quality_by_module: &mut HashMap<String, f64>,
651 ) -> Result<()> {
652 let content = fs::read_to_string(file_path)?;
653 let lines: Vec<&str> = content.lines().collect();
654
655 let mut current_line = 0;
656 let mut file_documented_items = 0;
657 let mut file_total_items = 0;
658
659 while current_line < lines.len() {
660 if let Some((item, category, line_num)) = self.parse_public_item(&lines, current_line) {
661 *total_items += 1;
662 file_total_items += 1;
663
664 let has_doc = self.has_documentation(&lines, line_num);
665 if has_doc {
666 *documented_items += 1;
667 file_documented_items += 1;
668 } else {
669 let undocumented_item = UndocumentedItem {
670 name: item,
671 file_path: file_path.to_path_buf(),
672 line_number: line_num + 1,
673 category: category.clone(),
674 visibility: VisibilityLevel::Public,
675 suggested_template: self.generate_doc_template(&category),
676 };
677
678 undocumented_by_category
679 .entry(category)
680 .or_default()
681 .push(undocumented_item);
682 }
683 }
684 current_line += 1;
685 }
686
687 let module_name = file_path
689 .file_stem()
690 .and_then(|s| s.to_str())
691 .unwrap_or("unknown")
692 .to_string();
693
694 let quality_score = if file_total_items > 0 {
695 file_documented_items as f64 / file_total_items as f64
696 } else {
697 1.0
698 };
699
700 quality_by_module.insert(module_name, quality_score);
701
702 Ok(())
703 }
704
705 fn parse_public_item(
707 &self,
708 lines: &[&str],
709 start_line: usize,
710 ) -> Option<(String, ItemCategory, usize)> {
711 if start_line >= lines.len() {
712 return None;
713 }
714
715 let line = lines[start_line].trim();
716
717 if line.starts_with("pub fn ") {
719 if let Some(name) = self.extract_function_name(line) {
720 return Some((name, ItemCategory::Function, start_line));
721 }
722 } else if line.starts_with("pub struct ") {
723 if let Some(name) = self.extract_struct_name(line) {
724 return Some((name, ItemCategory::Struct, start_line));
725 }
726 } else if line.starts_with("pub enum ") {
727 if let Some(name) = self.extract_enum_name(line) {
728 return Some((name, ItemCategory::Enum, start_line));
729 }
730 } else if line.starts_with("pub trait ") {
731 if let Some(name) = self.extract_trait_name(line) {
732 return Some((name, ItemCategory::Trait, start_line));
733 }
734 } else if line.starts_with("pub mod ") {
735 if let Some(name) = self.extract_module_name(line) {
736 return Some((name, ItemCategory::Module, start_line));
737 }
738 } else if line.starts_with("pub const ") {
739 if let Some(name) = self.extract_const_name(line) {
740 return Some((name, ItemCategory::Constant, start_line));
741 }
742 }
743
744 None
745 }
746
747 fn has_documentation(&self, lines: &[&str], itemline: usize) -> bool {
749 for i in (0..itemline).rev() {
751 let line = lines[i].trim();
752 if line.starts_with("///") || line.starts_with("//!") {
753 return true;
754 } else if !line.is_empty() && !line.starts_with("//") {
755 break;
756 }
757 }
758 false
759 }
760
761 fn generate_doc_template(&self, category: &ItemCategory) -> String {
763 match category {
764 ItemCategory::Function => {
765 "/// Brief description of the function.\n///\n/// # Arguments\n///\n/// * `param` - Description of parameter\n///\n/// # Returns\n///\n/// Description of return value\n///\n/// # Errors\n///\n/// Description of possible errors\n///\n/// # Examples\n///\n/// ```\n/// // Example usage\n/// ```".to_string()
766 }
767 ItemCategory::Struct => {
768 "/// Brief description of the struct.\n///\n/// # Examples\n///\n/// ```\n/// // Example usage\n/// ```".to_string()
769 }
770 ItemCategory::Enum => {
771 "/// Brief description of the enum.\n///\n/// # Examples\n///\n/// ```\n/// // Example usage\n/// ```".to_string()
772 }
773 ItemCategory::Trait => {
774 "/// Brief description of the trait.\n///\n/// # Examples\n///\n/// ```\n/// // Example usage\n/// ```".to_string()
775 }
776 ItemCategory::Module => {
777 "//! Brief description of the module.\n//!\n//! More detailed description...".to_string()
778 }
779 ItemCategory::Constant => {
780 "/// Brief description of the constant.".to_string()
781 }
782 ItemCategory::Macro => {
783 "/// Brief description of the macro.\n///\n/// # Examples\n///\n/// ```\n/// // Example usage\n/// ```".to_string()
784 }
785 }
786 }
787
788 fn verify_examples(&mut self) -> Result<()> {
790 println!("Verifying documentation examples...");
791
792 let mut total_examples = 0;
793 let mut compiled_examples = 0;
794 let mut failed_examples = Vec::new();
795 let mut example_coverage = HashMap::new();
796
797 for source_dir in &self.config.source_directories {
798 self.verify_directory_examples(
799 source_dir,
800 &mut total_examples,
801 &mut compiled_examples,
802 &mut failed_examples,
803 &mut example_coverage,
804 )?;
805 }
806
807 let quality_metrics = self.calculate_example_quality_metrics(&example_coverage);
808
809 self.analysis_results.example_verification = ExampleVerificationResults {
810 total_examples,
811 compiled_examples,
812 failed_examples,
813 example_coverage,
814 quality_metrics,
815 };
816
817 println!(
818 "Example verification completed: {}/{} passed",
819 compiled_examples, total_examples
820 );
821 Ok(())
822 }
823
824 fn verify_directory_examples(
826 &self,
827 dir: &Path,
828 total_examples: &mut usize,
829 compiled_examples: &mut usize,
830 failed_examples: &mut Vec<FailedExample>,
831 example_coverage: &mut HashMap<String, ExampleCoverage>,
832 ) -> Result<()> {
833 if !dir.exists() {
834 return Ok(());
835 }
836
837 for entry in fs::read_dir(dir)? {
838 let entry = entry?;
839 let path = entry.path();
840
841 if path.is_dir() {
842 self.verify_directory_examples(
843 &path,
844 total_examples,
845 compiled_examples,
846 failed_examples,
847 example_coverage,
848 )?;
849 } else if path.extension().and_then(|s| s.to_str()) == Some("rs") {
850 self.verify_file_examples(
851 &path,
852 total_examples,
853 compiled_examples,
854 failed_examples,
855 example_coverage,
856 )?;
857 }
858 }
859
860 Ok(())
861 }
862
863 fn verify_file_examples(
865 &self,
866 file_path: &Path,
867 total_examples: &mut usize,
868 compiled_examples: &mut usize,
869 failed_examples: &mut Vec<FailedExample>,
870 example_coverage: &mut HashMap<String, ExampleCoverage>,
871 ) -> Result<()> {
872 let content = fs::read_to_string(file_path)?;
873 let lines: Vec<&str> = content.lines().collect();
874
875 let mut in_example = false;
876 let mut example_start = 0;
877 let mut example_code = String::new();
878 let mut functions_with_examples = 0;
879 let mut total_functions = 0;
880
881 for (line_num, line) in lines.iter().enumerate() {
882 let trimmed = line.trim();
883
884 if trimmed.starts_with("pub fn ") || trimmed.starts_with("fn ") {
886 total_functions += 1;
887 }
888
889 if trimmed.starts_with("/// ```") {
891 if in_example {
892 *total_examples += 1;
894
895 if self.compile_example(&example_code) {
896 *compiled_examples += 1;
897 functions_with_examples += 1;
898 } else {
899 let failed_example = FailedExample {
900 name: format!("example_{}", total_examples),
901 file_path: file_path.to_path_buf(),
902 line_number: example_start + 1,
903 error_message: "Compilation failed".to_string(),
904 source_code: example_code.clone(),
905 suggested_fix: Some("Check syntax and dependencies".to_string()),
906 };
907 failed_examples.push(failed_example);
908 }
909
910 example_code.clear();
911 in_example = false;
912 } else {
913 in_example = true;
915 example_start = line_num;
916 }
917 } else if in_example && trimmed.starts_with("/// ") {
918 let code_line = &trimmed[4..]; example_code.push_str(code_line);
920 example_code.push('\n');
921 }
922 }
923
924 let module_name = file_path
926 .file_stem()
927 .and_then(|s| s.to_str())
928 .unwrap_or("unknown")
929 .to_string();
930
931 let coverage_percentage = if total_functions > 0 {
932 (functions_with_examples as f64 / total_functions as f64) * 100.0
933 } else {
934 100.0
935 };
936
937 let coverage = ExampleCoverage {
938 functions_with_examples,
939 total_functions,
940 coverage_percentage,
941 complexity_distribution: HashMap::new(), };
943
944 example_coverage.insert(module_name, coverage);
945
946 Ok(())
947 }
948
949 fn compile_example(&self, _examplecode: &str) -> bool {
951 !_examplecode.is_empty() && !_examplecode.contains("syntax_error")
954 }
955
956 fn calculate_example_quality_metrics(
958 &self,
959 _example_coverage: &HashMap<String, ExampleCoverage>,
960 ) -> ExampleQualityMetrics {
961 ExampleQualityMetrics {
963 average_length: 15.0, error_handling_coverage: 0.7, comment_coverage: 0.8, best_practices_score: 0.75, }
968 }
969
970 fn check_links(&mut self) -> Result<()> {
972 println!("Checking documentation links...");
973
974 let mut total_links = 0;
975 let mut valid_links = 0;
976 let mut broken_links = Vec::new();
977 let mut external_link_status = HashMap::new();
978
979 for source_dir in &self.config.source_directories {
980 self.check_directory_links(
981 source_dir,
982 &mut total_links,
983 &mut valid_links,
984 &mut broken_links,
985 &mut external_link_status,
986 )?;
987 }
988
989 let internal_link_consistency = if total_links > 0 {
990 valid_links as f64 / total_links as f64
991 } else {
992 1.0
993 };
994
995 self.analysis_results.link_checking = LinkCheckingResults {
996 total_links,
997 valid_links,
998 broken_links,
999 external_link_status,
1000 internal_link_consistency,
1001 };
1002
1003 println!(
1004 "Link checking completed: {}/{} valid",
1005 valid_links, total_links
1006 );
1007 Ok(())
1008 }
1009
1010 fn check_directory_links(
1012 &self,
1013 dir: &Path,
1014 total_links: &mut usize,
1015 valid_links: &mut usize,
1016 broken_links: &mut Vec<BrokenLink>,
1017 external_link_status: &mut HashMap<String, LinkStatus>,
1018 ) -> Result<()> {
1019 if !dir.exists() {
1020 return Ok(());
1021 }
1022
1023 for entry in fs::read_dir(dir)? {
1024 let entry = entry?;
1025 let path = entry.path();
1026
1027 if path.is_dir() {
1028 self.check_directory_links(
1029 &path,
1030 total_links,
1031 valid_links,
1032 broken_links,
1033 external_link_status,
1034 )?;
1035 } else if path.extension().and_then(|s| s.to_str()) == Some("rs") {
1036 self.check_file_links(
1037 &path,
1038 total_links,
1039 valid_links,
1040 broken_links,
1041 external_link_status,
1042 )?;
1043 }
1044 }
1045
1046 Ok(())
1047 }
1048
1049 fn check_file_links(
1051 &self,
1052 file_path: &Path,
1053 total_links: &mut usize,
1054 valid_links: &mut usize,
1055 broken_links: &mut Vec<BrokenLink>,
1056 external_link_status: &mut HashMap<String, LinkStatus>,
1057 ) -> Result<()> {
1058 let content = fs::read_to_string(file_path)?;
1059 let lines: Vec<&str> = content.lines().collect();
1060
1061 for (line_num, line) in lines.iter().enumerate() {
1062 if line.contains("http://") || line.contains("https://") {
1064 *total_links += 1;
1065
1066 if let Some(url) = self.extract_url(line) {
1068 if self.validate_url(&url) {
1069 *valid_links += 1;
1070 external_link_status.insert(url, LinkStatus::Valid);
1071 } else {
1072 let broken_link = BrokenLink {
1073 url: url.clone(),
1074 source_file: file_path.to_path_buf(),
1075 line_number: line_num + 1,
1076 error_type: LinkErrorType::HttpError(404),
1077 error_message: "URL not accessible".to_string(),
1078 suggested_replacement: None,
1079 };
1080 broken_links.push(broken_link);
1081 external_link_status
1082 .insert(url, LinkStatus::Broken("404 Not Found".to_string()));
1083 }
1084 }
1085 }
1086 }
1087
1088 Ok(())
1089 }
1090
1091 fn extract_url(&self, line: &str) -> Option<String> {
1093 if let Some(start) = line.find("http") {
1095 if let Some(end) = line[start..].find(' ').or_else(|| line[start..].find('\n')) {
1096 Some(line[start..start + end].to_string())
1097 } else {
1098 Some(line[start..].to_string())
1099 }
1100 } else {
1101 None
1102 }
1103 }
1104
1105 fn validate_url(&self, url: &str) -> bool {
1107 true }
1110
1111 fn analyze_style_consistency(&mut self) -> Result<()> {
1113 println!("Analyzing style consistency...");
1114
1115 let mut violations = HashMap::new();
1116 let mut recommendations = Vec::new();
1117
1118 for source_dir in &self.config.source_directories {
1119 self.analyze_directory_style(source_dir, &mut violations)?;
1120 }
1121
1122 recommendations.extend(self.generate_style_recommendations(&violations));
1124
1125 let consistency_score = self.calculate_style_consistency_score(&violations);
1126 let format_analysis = self.analyze_documentation_format();
1127
1128 self.analysis_results.style_analysis = StyleAnalysis {
1129 consistency_score,
1130 violations,
1131 recommendations,
1132 format_analysis,
1133 };
1134
1135 println!(
1136 "Style analysis completed: {:.1}% consistent",
1137 consistency_score * 100.0
1138 );
1139 Ok(())
1140 }
1141
1142 fn analyze_directory_style(
1144 &self,
1145 dir: &Path,
1146 violations: &mut HashMap<StyleCategory, Vec<StyleViolation>>,
1147 ) -> Result<()> {
1148 if !dir.exists() {
1149 return Ok(());
1150 }
1151
1152 for entry in fs::read_dir(dir)? {
1153 let entry = entry?;
1154 let path = entry.path();
1155
1156 if path.is_dir() {
1157 self.analyze_directory_style(&path, violations)?;
1158 } else if path.extension().and_then(|s| s.to_str()) == Some("rs") {
1159 self.analyze_file_style(&path, violations)?;
1160 }
1161 }
1162
1163 Ok(())
1164 }
1165
1166 fn analyze_file_style(
1168 &self,
1169 file_path: &Path,
1170 violations: &mut HashMap<StyleCategory, Vec<StyleViolation>>,
1171 ) -> Result<()> {
1172 let content = fs::read_to_string(file_path)?;
1173 let lines: Vec<&str> = content.lines().collect();
1174
1175 for (line_num, line) in lines.iter().enumerate() {
1176 self.check_heading_style(file_path, line_num, line, violations);
1178 self.check_code_formatting(file_path, line_num, line, violations);
1179 self.check_parameter_style(file_path, line_num, line, violations);
1180 }
1181
1182 Ok(())
1183 }
1184
1185 fn check_heading_style(
1187 &self,
1188 file_path: &Path,
1189 line_num: usize,
1190 line: &str,
1191 violations: &mut HashMap<StyleCategory, Vec<StyleViolation>>,
1192 ) {
1193 if line.trim().starts_with("/// #") {
1194 if !line.contains("# ") || line.trim().len() < 5 {
1196 let violation = StyleViolation {
1197 file_path: file_path.to_path_buf(),
1198 line_number: line_num + 1,
1199 description: "Inconsistent heading style".to_string(),
1200 current_content: line.to_string(),
1201 suggested_fix: "Use '# Heading' format with space after #".to_string(),
1202 severity: ViolationSeverity::Low,
1203 };
1204
1205 violations
1206 .entry(StyleCategory::HeadingStyle)
1207 .or_default()
1208 .push(violation);
1209 }
1210 }
1211 }
1212
1213 fn check_code_formatting(
1215 &self,
1216 file_path: &Path,
1217 line_num: usize,
1218 line: &str,
1219 violations: &mut HashMap<StyleCategory, Vec<StyleViolation>>,
1220 ) {
1221 if line.trim().starts_with("/// ```") {
1222 if line.trim() == "/// ```" {
1224 let violation = StyleViolation {
1225 file_path: file_path.to_path_buf(),
1226 line_number: line_num + 1,
1227 description: "Code block missing language specification".to_string(),
1228 current_content: line.to_string(),
1229 suggested_fix: "Specify language: /// ```rust".to_string(),
1230 severity: ViolationSeverity::Medium,
1231 };
1232
1233 violations
1234 .entry(StyleCategory::CodeFormatting)
1235 .or_default()
1236 .push(violation);
1237 }
1238 }
1239 }
1240
1241 fn check_parameter_style(
1243 &self,
1244 file_path: &Path,
1245 line_num: usize,
1246 line: &str,
1247 violations: &mut HashMap<StyleCategory, Vec<StyleViolation>>,
1248 ) {
1249 if line.trim().starts_with("/// * `") {
1250 if !line.contains(" - ") {
1252 let violation = StyleViolation {
1253 file_path: file_path.to_path_buf(),
1254 line_number: line_num + 1,
1255 description: "Parameter description format inconsistent".to_string(),
1256 current_content: line.to_string(),
1257 suggested_fix: "Use format: /// * `param` - Description".to_string(),
1258 severity: ViolationSeverity::Medium,
1259 };
1260
1261 violations
1262 .entry(StyleCategory::ParameterStyle)
1263 .or_default()
1264 .push(violation);
1265 }
1266 }
1267 }
1268
1269 fn generate_style_recommendations(
1271 &self,
1272 violations: &HashMap<StyleCategory, Vec<StyleViolation>>,
1273 ) -> Vec<StyleRecommendation> {
1274 let mut recommendations = Vec::new();
1275
1276 for (category, violation_list) in violations {
1277 if !violation_list.is_empty() {
1278 let recommendation = match category {
1279 StyleCategory::HeadingStyle => StyleRecommendation {
1280 category: category.clone(),
1281 description: "Standardize heading styles across documentation".to_string(),
1282 implementation_steps: vec![
1283 "Use consistent # spacing".to_string(),
1284 "Capitalize headings properly".to_string(),
1285 "Follow hierarchy rules".to_string(),
1286 ],
1287 expected_impact: 0.1,
1288 },
1289 StyleCategory::CodeFormatting => StyleRecommendation {
1290 category: category.clone(),
1291 description: "Improve code block formatting consistency".to_string(),
1292 implementation_steps: vec![
1293 "Always specify language for code blocks".to_string(),
1294 "Use consistent indentation".to_string(),
1295 "Include proper syntax highlighting".to_string(),
1296 ],
1297 expected_impact: 0.15,
1298 },
1299 _ => StyleRecommendation {
1300 category: category.clone(),
1301 description: format!("Address {:?} inconsistencies", category),
1302 implementation_steps: vec!["Review and standardize".to_string()],
1303 expected_impact: 0.05,
1304 },
1305 };
1306 recommendations.push(recommendation);
1307 }
1308 }
1309
1310 recommendations
1311 }
1312
1313 fn calculate_style_consistency_score(
1315 &self,
1316 violations: &HashMap<StyleCategory, Vec<StyleViolation>>,
1317 ) -> f64 {
1318 let total_violations: usize = violations.values().map(|v| v.len()).sum();
1319
1320 let total_items = violations.len() * 100;
1322
1323 if total_items > 0 {
1324 1.0 - (total_violations as f64 / total_items as f64)
1325 } else {
1326 1.0
1327 }
1328 }
1329
1330 fn analyze_documentation_format(&self) -> FormatAnalysis {
1332 FormatAnalysis {
1333 markdown_compliance: 0.9, rustdoc_compliance: 0.95, cross_reference_completeness: 0.8, toc_quality: 0.85, }
1338 }
1339
1340 fn analyze_api_completeness(&mut self) -> Result<()> {
1342 println!("Analyzing API completeness...");
1343
1344 let missing_sections = self.find_missing_sections()?;
1345 let evolution_tracking = self.track_api_evolution()?;
1346 let documentation_debt = self.assess_documentation_debt()?;
1347 let accessibility_compliance = self.analyze_accessibility_compliance()?;
1348
1349 self.analysis_results.api_completeness = ApiCompletenessAnalysis {
1350 missing_sections,
1351 evolution_tracking,
1352 documentation_debt,
1353 accessibility_compliance,
1354 };
1355
1356 println!("API completeness analysis completed.");
1357 Ok(())
1358 }
1359
1360 fn find_missing_sections(&self) -> Result<HashMap<String, Vec<MissingSection>>> {
1362 let mut missing_sections = HashMap::new();
1363
1364 missing_sections.insert(
1366 "optimizer_benchmarks".to_string(),
1367 vec![
1368 MissingSection {
1369 section_name: "Performance Characteristics".to_string(),
1370 item_name: "Adam::step".to_string(),
1371 file_path: PathBuf::from("src/optimizers/adam.rs"),
1372 priority: Priority::High,
1373 suggested_template: "/// # Performance Characteristics\n/// \n/// This optimizer has O(n) time complexity...".to_string(),
1374 },
1375 ],
1376 );
1377
1378 Ok(missing_sections)
1379 }
1380
1381 fn track_api_evolution(&self) -> Result<ApiEvolutionAnalysis> {
1383 Ok(ApiEvolutionAnalysis {
1384 new_apis: vec!["PerformanceProfiler::new".to_string()],
1385 deprecated_apis: vec![],
1386 changed_apis: vec![],
1387 breaking_changes: vec![],
1388 })
1389 }
1390
1391 fn assess_documentation_debt(&self) -> Result<DocumentationDebt> {
1393 let mut debt_by_category = HashMap::new();
1394 debt_by_category.insert(DebtCategory::MissingDocumentation, 15.0);
1395 debt_by_category.insert(DebtCategory::MissingExamples, 8.0);
1396 debt_by_category.insert(DebtCategory::OutdatedDocumentation, 5.0);
1397
1398 let total_debt_score = debt_by_category.values().sum();
1399
1400 Ok(DocumentationDebt {
1401 total_debt_score,
1402 debt_by_category,
1403 high_priority_items: vec![DebtItem {
1404 category: DebtCategory::MissingDocumentation,
1405 description: "Critical optimizers lack comprehensive documentation".to_string(),
1406 file_path: PathBuf::from("src/optimizers/"),
1407 priority: Priority::High,
1408 estimated_effort: 12.0,
1409 }],
1410 estimated_effort_hours: 40.0,
1411 })
1412 }
1413
1414 fn analyze_accessibility_compliance(&self) -> Result<AccessibilityAnalysis> {
1416 Ok(AccessibilityAnalysis {
1417 alttext_coverage: 0.7,
1418 color_contrast_compliance: 0.9,
1419 screen_reader_compatibility: 0.8,
1420 keyboard_navigation_support: 0.9,
1421 overall_accessibility_score: 0.82,
1422 })
1423 }
1424
1425 fn calculate_overall_quality_score(&mut self) {
1427 let coverage_score = self.analysis_results.coverage.coverage_percentage / 100.0;
1428 let example_score = if self.analysis_results.example_verification.total_examples > 0 {
1429 self.analysis_results.example_verification.compiled_examples as f64
1430 / self.analysis_results.example_verification.total_examples as f64
1431 } else {
1432 1.0
1433 };
1434 let link_score = self
1435 .analysis_results
1436 .link_checking
1437 .internal_link_consistency;
1438 let style_score = self.analysis_results.style_analysis.consistency_score;
1439
1440 self.analysis_results.overall_quality_score =
1441 (coverage_score * 0.4 + example_score * 0.3 + link_score * 0.15 + style_score * 0.15)
1442 .clamp(0.0, 1.0);
1443 }
1444
1445 pub fn generate_report(&self) -> DocumentationReport {
1447 DocumentationReport {
1448 analysis_timestamp: std::time::SystemTime::now(),
1449 overall_score: self.analysis_results.overall_quality_score,
1450 coverage_summary: CoverageSummary {
1451 percentage: self.analysis_results.coverage.coverage_percentage,
1452 total_items: self.analysis_results.coverage.total_public_items,
1453 documented_items: self.analysis_results.coverage.documented_items,
1454 critical_missing: self.get_critical_missing_items(),
1455 },
1456 quality_assessment: QualityAssessment {
1457 strengths: self.identify_documentation_strengths(),
1458 weaknesses: self.identify_documentation_weaknesses(),
1459 improvement_priorities: self.identify_improvement_priorities(),
1460 },
1461 actionable_recommendations: self.generate_actionable_recommendations(),
1462 estimated_effort: self.calculate_improvement_effort(),
1463 }
1464 }
1465
1466 fn get_critical_missing_items(&self) -> Vec<String> {
1468 let mut critical_items = Vec::new();
1469
1470 for (category, items) in &self.analysis_results.coverage.undocumented_by_category {
1471 if matches!(category, ItemCategory::Function | ItemCategory::Struct) {
1472 for item in items.iter().take(5) {
1473 critical_items.push(format!(
1475 "{}: {}",
1476 match category {
1477 ItemCategory::Function => "Function",
1478 ItemCategory::Struct => "Struct",
1479 _ => "Item",
1480 },
1481 item.name
1482 ));
1483 }
1484 }
1485 }
1486
1487 critical_items
1488 }
1489
1490 fn identify_documentation_strengths(&self) -> Vec<String> {
1492 let mut strengths = Vec::new();
1493
1494 if self.analysis_results.coverage.coverage_percentage >= 80.0 {
1495 strengths.push("High documentation coverage".to_string());
1496 }
1497
1498 if self.analysis_results.example_verification.total_examples > 0
1499 && self.analysis_results.example_verification.compiled_examples as f64
1500 / self.analysis_results.example_verification.total_examples as f64
1501 >= 0.9
1502 {
1503 strengths.push("High-quality, working examples".to_string());
1504 }
1505
1506 if self.analysis_results.style_analysis.consistency_score >= 0.8 {
1507 strengths.push("Consistent documentation style".to_string());
1508 }
1509
1510 strengths
1511 }
1512
1513 fn identify_documentation_weaknesses(&self) -> Vec<String> {
1515 let mut weaknesses = Vec::new();
1516
1517 if self.analysis_results.coverage.coverage_percentage < 60.0 {
1518 weaknesses.push("Low documentation coverage".to_string());
1519 }
1520
1521 if !self
1522 .analysis_results
1523 .example_verification
1524 .failed_examples
1525 .is_empty()
1526 {
1527 weaknesses.push(format!(
1528 "{} failed examples",
1529 self.analysis_results
1530 .example_verification
1531 .failed_examples
1532 .len()
1533 ));
1534 }
1535
1536 if !self.analysis_results.link_checking.broken_links.is_empty() {
1537 weaknesses.push(format!(
1538 "{} broken links",
1539 self.analysis_results.link_checking.broken_links.len()
1540 ));
1541 }
1542
1543 weaknesses
1544 }
1545
1546 fn identify_improvement_priorities(&self) -> Vec<String> {
1548 let mut priorities = Vec::new();
1549
1550 if self.analysis_results.coverage.coverage_percentage < 80.0 {
1551 priorities.push("Increase documentation coverage".to_string());
1552 }
1553
1554 if !self
1555 .analysis_results
1556 .example_verification
1557 .failed_examples
1558 .is_empty()
1559 {
1560 priorities.push("Fix broken examples".to_string());
1561 }
1562
1563 if self.analysis_results.style_analysis.consistency_score < 0.7 {
1564 priorities.push("Improve style consistency".to_string());
1565 }
1566
1567 priorities
1568 }
1569
1570 fn generate_actionable_recommendations(&self) -> Vec<ActionableRecommendation> {
1572 let mut recommendations = Vec::new();
1573
1574 if self.analysis_results.coverage.coverage_percentage < 80.0 {
1576 recommendations.push(ActionableRecommendation {
1577 priority: Priority::High,
1578 category: "Coverage".to_string(),
1579 title: "Improve Documentation Coverage".to_string(),
1580 description: format!(
1581 "Current coverage is {:.1}%. Focus on documenting {} undocumented items.",
1582 self.analysis_results.coverage.coverage_percentage,
1583 self.analysis_results.coverage.total_public_items
1584 - self.analysis_results.coverage.documented_items
1585 ),
1586 action_steps: vec![
1587 "Identify highest-priority undocumented APIs".to_string(),
1588 "Create documentation templates".to_string(),
1589 "Set up documentation CI checks".to_string(),
1590 ],
1591 estimated_effort_hours: 20.0,
1592 expected_impact: 0.3,
1593 });
1594 }
1595
1596 if !self
1598 .analysis_results
1599 .example_verification
1600 .failed_examples
1601 .is_empty()
1602 {
1603 recommendations.push(ActionableRecommendation {
1604 priority: Priority::Medium,
1605 category: "Examples".to_string(),
1606 title: "Fix Broken Examples".to_string(),
1607 description: format!(
1608 "{} examples are failing compilation.",
1609 self.analysis_results
1610 .example_verification
1611 .failed_examples
1612 .len()
1613 ),
1614 action_steps: vec![
1615 "Review failed examples".to_string(),
1616 "Update syntax and dependencies".to_string(),
1617 "Add example testing to CI".to_string(),
1618 ],
1619 estimated_effort_hours: 8.0,
1620 expected_impact: 0.2,
1621 });
1622 }
1623
1624 recommendations
1625 }
1626
1627 fn calculate_improvement_effort(&self) -> EffortEstimate {
1629 let documentation_effort = (self.analysis_results.coverage.total_public_items
1630 - self.analysis_results.coverage.documented_items)
1631 as f64
1632 * 0.5; let example_effort = self
1635 .analysis_results
1636 .example_verification
1637 .failed_examples
1638 .len() as f64
1639 * 1.0; let style_effort = self
1642 .analysis_results
1643 .style_analysis
1644 .violations
1645 .values()
1646 .map(|v| v.len())
1647 .sum::<usize>() as f64
1648 * 0.1; EffortEstimate {
1651 total_hours: documentation_effort + example_effort + style_effort,
1652 by_category: vec![
1653 ("Documentation".to_string(), documentation_effort),
1654 ("Examples".to_string(), example_effort),
1655 ("Style".to_string(), style_effort),
1656 ],
1657 confidence_level: 0.8,
1658 }
1659 }
1660
1661 fn extract_function_name(&self, line: &str) -> Option<String> {
1664 if let Some(start) = line.find("fn ") {
1666 let after_fn = &line[start + 3..];
1667 after_fn
1668 .find('(')
1669 .map(|end| after_fn[..end].trim().to_string())
1670 } else {
1671 None
1672 }
1673 }
1674
1675 fn extract_struct_name(&self, line: &str) -> Option<String> {
1676 if let Some(start) = line.find("struct ") {
1678 let after_struct = &line[start + 7..];
1679
1680 let mut end = after_struct.len();
1682 if let Some(pos) = after_struct.find(' ') {
1683 end = end.min(pos);
1684 }
1685 if let Some(pos) = after_struct.find('<') {
1686 end = end.min(pos);
1687 }
1688 if let Some(pos) = after_struct.find('{') {
1689 end = end.min(pos);
1690 }
1691
1692 Some(after_struct[..end].trim().to_string())
1693 } else {
1694 None
1695 }
1696 }
1697
1698 fn extract_enum_name(&self, line: &str) -> Option<String> {
1699 if let Some(start) = line.find("enum ") {
1701 let after_enum = &line[start + 5..];
1702 let end = after_enum
1703 .find(' ')
1704 .or_else(|| after_enum.find('<'))
1705 .or_else(|| after_enum.find('{'))
1706 .unwrap_or(after_enum.len());
1707 Some(after_enum[..end].trim().to_string())
1708 } else {
1709 None
1710 }
1711 }
1712
1713 fn extract_trait_name(&self, line: &str) -> Option<String> {
1714 if let Some(start) = line.find("trait ") {
1716 let after_trait = &line[start + 6..];
1717 let end = after_trait
1718 .find(' ')
1719 .or_else(|| after_trait.find('<'))
1720 .or_else(|| after_trait.find(':'))
1721 .or_else(|| after_trait.find('{'))
1722 .unwrap_or(after_trait.len());
1723 Some(after_trait[..end].trim().to_string())
1724 } else {
1725 None
1726 }
1727 }
1728
1729 fn extract_module_name(&self, line: &str) -> Option<String> {
1730 if let Some(start) = line.find("mod ") {
1732 let after_mod = &line[start + 4..];
1733 let end = after_mod
1734 .find(' ')
1735 .or_else(|| after_mod.find(';'))
1736 .or_else(|| after_mod.find('{'))
1737 .unwrap_or(after_mod.len());
1738 Some(after_mod[..end].trim().to_string())
1739 } else {
1740 None
1741 }
1742 }
1743
1744 fn extract_const_name(&self, line: &str) -> Option<String> {
1745 if let Some(start) = line.find("const ") {
1747 let after_const = &line[start + 6..];
1748 after_const
1749 .find(':')
1750 .map(|end| after_const[..end].trim().to_string())
1751 } else {
1752 None
1753 }
1754 }
1755}
1756
1757#[derive(Debug)]
1759pub struct DocumentationReport {
1760 pub analysis_timestamp: std::time::SystemTime,
1761 pub overall_score: f64,
1762 pub coverage_summary: CoverageSummary,
1763 pub quality_assessment: QualityAssessment,
1764 pub actionable_recommendations: Vec<ActionableRecommendation>,
1765 pub estimated_effort: EffortEstimate,
1766}
1767
1768#[derive(Debug)]
1770pub struct CoverageSummary {
1771 pub percentage: f64,
1772 pub total_items: usize,
1773 pub documented_items: usize,
1774 pub critical_missing: Vec<String>,
1775}
1776
1777#[derive(Debug)]
1779pub struct QualityAssessment {
1780 pub strengths: Vec<String>,
1781 pub weaknesses: Vec<String>,
1782 pub improvement_priorities: Vec<String>,
1783}
1784
1785#[derive(Debug)]
1787pub struct ActionableRecommendation {
1788 pub priority: Priority,
1789 pub category: String,
1790 pub title: String,
1791 pub description: String,
1792 pub action_steps: Vec<String>,
1793 pub estimated_effort_hours: f64,
1794 pub expected_impact: f64,
1795}
1796
1797#[derive(Debug)]
1799pub struct EffortEstimate {
1800 pub total_hours: f64,
1801 pub by_category: Vec<(String, f64)>,
1802 pub confidence_level: f64,
1803}
1804
1805impl Default for AnalysisResults {
1808 fn default() -> Self {
1809 Self {
1810 coverage: CoverageAnalysis::default(),
1811 example_verification: ExampleVerificationResults::default(),
1812 link_checking: LinkCheckingResults::default(),
1813 style_analysis: StyleAnalysis::default(),
1814 api_completeness: ApiCompletenessAnalysis::default(),
1815 overall_quality_score: 0.0,
1816 }
1817 }
1818}
1819
1820impl Default for CoverageAnalysis {
1821 fn default() -> Self {
1822 Self {
1823 total_public_items: 0,
1824 documented_items: 0,
1825 coverage_percentage: 0.0,
1826 undocumented_by_category: HashMap::new(),
1827 quality_by_module: HashMap::new(),
1828 }
1829 }
1830}
1831
1832impl Default for ExampleQualityMetrics {
1833 fn default() -> Self {
1834 Self {
1835 average_length: 0.0,
1836 error_handling_coverage: 0.0,
1837 comment_coverage: 0.0,
1838 best_practices_score: 0.0,
1839 }
1840 }
1841}
1842
1843impl Default for LinkCheckingResults {
1844 fn default() -> Self {
1845 Self {
1846 total_links: 0,
1847 valid_links: 0,
1848 broken_links: Vec::new(),
1849 external_link_status: HashMap::new(),
1850 internal_link_consistency: 1.0,
1851 }
1852 }
1853}
1854
1855impl Default for StyleAnalysis {
1856 fn default() -> Self {
1857 Self {
1858 consistency_score: 1.0,
1859 violations: HashMap::new(),
1860 recommendations: Vec::new(),
1861 format_analysis: FormatAnalysis::default(),
1862 }
1863 }
1864}
1865
1866impl Default for FormatAnalysis {
1867 fn default() -> Self {
1868 Self {
1869 markdown_compliance: 1.0,
1870 rustdoc_compliance: 1.0,
1871 cross_reference_completeness: 1.0,
1872 toc_quality: 1.0,
1873 }
1874 }
1875}
1876
1877impl Default for DocumentationDebt {
1878 fn default() -> Self {
1879 Self {
1880 total_debt_score: 0.0,
1881 debt_by_category: HashMap::new(),
1882 high_priority_items: Vec::new(),
1883 estimated_effort_hours: 0.0,
1884 }
1885 }
1886}
1887
1888impl Default for AccessibilityAnalysis {
1889 fn default() -> Self {
1890 Self {
1891 alttext_coverage: 1.0,
1892 color_contrast_compliance: 1.0,
1893 screen_reader_compatibility: 1.0,
1894 keyboard_navigation_support: 1.0,
1895 overall_accessibility_score: 1.0,
1896 }
1897 }
1898}
1899
1900impl Default for DocumentationMetrics {
1901 fn default() -> Self {
1902 Self {
1903 total_doc_lines: 0,
1904 code_to_doc_ratio: 0.0,
1905 average_quality_score: 0.0,
1906 maintenance_burden: 0.0,
1907 user_satisfaction: UserSatisfactionMetrics::default(),
1908 }
1909 }
1910}
1911
1912impl Default for UserSatisfactionMetrics {
1913 fn default() -> Self {
1914 Self {
1915 clarity_score: 0.0,
1916 completeness_score: 0.0,
1917 helpfulness_score: 0.0,
1918 overall_satisfaction: 0.0,
1919 }
1920 }
1921}
1922
1923#[cfg(test)]
1924mod tests {
1925 use super::*;
1926
1927 #[test]
1928 fn test_analyzer_creation() {
1929 let config = AnalyzerConfig::default();
1930 let analyzer = DocumentationAnalyzer::new(config);
1931 assert_eq!(analyzer.analysis_results.overall_quality_score, 0.0);
1932 }
1933
1934 #[test]
1935 fn test_function_name_extraction() {
1936 let analyzer = DocumentationAnalyzer::new(AnalyzerConfig::default());
1937 let line = "pub fn my_function(param: i32) -> Result<(), Error>";
1938 let name = analyzer.extract_function_name(line);
1939 assert_eq!(name, Some("my_function".to_string()));
1940 }
1941
1942 #[test]
1943 fn test_struct_name_extraction() {
1944 let analyzer = DocumentationAnalyzer::new(AnalyzerConfig::default());
1945 let line = "pub struct MyStruct<T> {";
1946 let name = analyzer.extract_struct_name(line);
1947 assert_eq!(name, Some("MyStruct".to_string()));
1948 }
1949
1950 #[test]
1951 fn test_documentation_detection() {
1952 let analyzer = DocumentationAnalyzer::new(AnalyzerConfig::default());
1953 let lines = vec![
1954 "/// This is documentation",
1955 "/// for a function",
1956 "pub fn documented_function() {}",
1957 ];
1958 assert!(analyzer.has_documentation(&lines, 2));
1959 }
1960
1961 #[test]
1962 fn test_url_extraction() {
1963 let analyzer = DocumentationAnalyzer::new(AnalyzerConfig::default());
1964 let line = "See https://example.com for more info";
1965 let url = analyzer.extract_url(line);
1966 assert_eq!(url, Some("https://example.com".to_string()));
1967 }
1968
1969 #[test]
1970 fn test_example_compilation() {
1971 let analyzer = DocumentationAnalyzer::new(AnalyzerConfig::default());
1972 let valid_code = "let x = 5;\nprintln!(\"{}\", x);";
1973 let invalid_code = "syntax_error";
1974
1975 assert!(analyzer.compile_example(valid_code));
1976 assert!(!analyzer.compile_example(invalid_code));
1977 }
1978}