covguard_directives/
lib.rs1use std::collections::{BTreeMap, BTreeSet};
7
8use covguard_ports::{ChangedRanges, RepoReader};
9
10pub fn has_ignore_directive(line: &str) -> bool {
22 let line_lower = line.to_lowercase();
23
24 if let Some(pos) = line_lower.find("covguard:") {
25 let after = &line_lower[pos + 9..]; let trimmed = after.trim_start();
27 return trimmed.starts_with("ignore");
28 }
29
30 if let Some(pos) = line_lower.find("covguard-ignore") {
31 let before = &line_lower[..pos];
32 return before.contains("//")
33 || before.contains('#')
34 || before.contains("--")
35 || before.contains("/*");
36 }
37
38 false
39}
40
41pub fn detect_ignored_lines<R: RepoReader>(
46 changed_ranges: &ChangedRanges,
47 reader: &R,
48) -> BTreeMap<String, BTreeSet<u32>> {
49 let mut ignored = BTreeMap::new();
50
51 for (path, ranges) in changed_ranges {
52 let mut file_ignored = BTreeSet::new();
53
54 for range in ranges {
55 for line_no in range.clone() {
56 if let Some(line_content) = reader.read_line(path, line_no)
57 && has_ignore_directive(&line_content)
58 {
59 file_ignored.insert(line_no);
60 }
61 }
62 }
63
64 if !file_ignored.is_empty() {
65 ignored.insert(path.clone(), file_ignored);
66 }
67 }
68
69 ignored
70}
71
72#[cfg(test)]
73mod tests {
74 use super::*;
75 use covguard_ports::RepoReader;
76 use std::collections::BTreeMap;
77
78 struct MapReader {
79 lines: BTreeMap<(String, u32), String>,
80 }
81
82 impl MapReader {
83 fn new(entries: Vec<(&str, u32, &str)>) -> Self {
84 let mut lines = BTreeMap::new();
85 for (path, line_no, content) in entries {
86 lines.insert((path.to_string(), line_no), content.to_string());
87 }
88 Self { lines }
89 }
90 }
91
92 impl RepoReader for MapReader {
93 fn read_line(&self, path: &str, line_no: u32) -> Option<String> {
94 self.lines.get(&(path.to_string(), line_no)).cloned()
95 }
96 }
97
98 #[test]
99 fn test_has_ignore_directive_rust_comment() {
100 assert!(has_ignore_directive("let x = 1; // covguard: ignore"));
101 assert!(has_ignore_directive("// covguard: ignore"));
102 assert!(has_ignore_directive(" // covguard: ignore"));
103 assert!(has_ignore_directive("// COVGUARD: IGNORE"));
104 assert!(has_ignore_directive("// covguard:ignore"));
105 }
106
107 #[test]
108 fn test_has_ignore_directive_python_comment() {
109 assert!(has_ignore_directive("x = 1 # covguard: ignore"));
110 assert!(has_ignore_directive("# covguard: ignore"));
111 assert!(has_ignore_directive("#covguard:ignore"));
112 }
113
114 #[test]
115 fn test_has_ignore_directive_block_comment() {
116 assert!(has_ignore_directive("/* covguard: ignore */"));
117 assert!(has_ignore_directive("int x = 1; /* covguard: ignore */"));
118 }
119
120 #[test]
121 fn test_has_ignore_directive_sql_comment() {
122 assert!(has_ignore_directive("-- covguard: ignore"));
123 assert!(has_ignore_directive("SELECT 1; -- covguard: ignore"));
124 }
125
126 #[test]
127 fn test_has_ignore_directive_hyphen_syntax() {
128 assert!(has_ignore_directive("// covguard-ignore"));
129 assert!(has_ignore_directive("# covguard-ignore"));
130 assert!(has_ignore_directive("-- covguard-ignore"));
131 assert!(has_ignore_directive("/* covguard-ignore */"));
132 }
133
134 #[test]
135 fn test_has_ignore_directive_negative_cases() {
136 assert!(!has_ignore_directive("let x = 1;"));
137 assert!(!has_ignore_directive("// some other comment"));
138 assert!(!has_ignore_directive("// covguard")); assert!(!has_ignore_directive("// ignore covguard")); }
141
142 #[test]
143 fn test_detect_ignored_lines_with_reader() {
144 let mut changed_ranges = BTreeMap::new();
145 changed_ranges.insert("src/lib.rs".to_string(), vec![1..=3]);
146 changed_ranges.insert("src/main.rs".to_string(), vec![10..=11]);
147
148 let reader = MapReader::new(vec![
149 ("src/lib.rs", 2, "let x = 1; // covguard: ignore"),
150 ("src/main.rs", 11, "# covguard: ignore"),
151 ]);
152
153 let ignored = detect_ignored_lines(&changed_ranges, &reader);
154 assert_eq!(
155 ignored.get("src/lib.rs").cloned(),
156 Some(BTreeSet::from([2]))
157 );
158 assert_eq!(
159 ignored.get("src/main.rs").cloned(),
160 Some(BTreeSet::from([11]))
161 );
162 }
163}