1use std::fmt;
2use std::ops::Add;
3
4use crate::lines::LineKind;
5use crate::workspace::{CrateSource, SourceFile};
6
7#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
8pub struct CommentDensity {
9 pub code_lines: usize,
10 pub comment_lines: usize,
11}
12
13impl CommentDensity {
14 #[must_use]
15 pub fn of_file(file: &SourceFile) -> Self {
16 let count = |wanted: LineKind| file.lines.iter().filter(|kind| **kind == wanted).count();
17 Self {
18 code_lines: count(LineKind::Code),
19 comment_lines: count(LineKind::Comment),
20 }
21 }
22
23 #[must_use]
24 pub fn of_crate(crate_source: &CrateSource) -> Self {
25 crate_source
26 .files
27 .iter()
28 .map(Self::of_file)
29 .fold(Self::default(), Add::add)
30 }
31
32 #[must_use]
33 pub fn within_limit(self) -> bool {
34 self.comment_lines <= crate::structure::FREE_COMMENT_LINES
35 || self.ratio() <= crate::structure::MAX_COMMENT_RATIO
36 }
37
38 #[must_use]
39 pub fn ratio(self) -> f64 {
40 if self.code_lines == 0 {
41 return 0.0;
42 }
43 crate::count_as_f64(self.comment_lines) / crate::count_as_f64(self.code_lines)
44 }
45}
46
47impl Add for CommentDensity {
48 type Output = Self;
49
50 fn add(self, other: Self) -> Self {
51 Self {
52 code_lines: self.code_lines + other.code_lines,
53 comment_lines: self.comment_lines + other.comment_lines,
54 }
55 }
56}
57
58impl fmt::Display for CommentDensity {
59 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
60 write!(
61 formatter,
62 "ratio {:.3} ({} comment / {} code lines)",
63 self.ratio(),
64 self.comment_lines,
65 self.code_lines
66 )
67 }
68}
69
70#[cfg(test)]
71mod tests {
72 use std::path::PathBuf;
73
74 use super::CommentDensity;
75 use crate::workspace::SourceFile;
76
77 fn density(text: &str) -> CommentDensity {
78 CommentDensity::of_file(&SourceFile::parse(PathBuf::from("lib.rs"), text).unwrap())
79 }
80
81 #[test]
82 fn counts_code_and_comment_lines() {
83 let density = density("// why\nfn a() {}\n\nfn b() {} // trailing\n");
84 assert_eq!(
85 density,
86 CommentDensity {
87 code_lines: 2,
88 comment_lines: 1
89 }
90 );
91 assert!((density.ratio() - 0.5).abs() < f64::EPSILON);
92 }
93
94 #[test]
95 fn source_without_code_has_zero_ratio() {
96 assert!(density("// only a comment").ratio().abs() < f64::EPSILON);
97 }
98}