wowlab_tidy/infra/
helpers.rs1#[cfg(test)]
2use googletest::prelude::*;
3
4use super::parse;
5
6const MAX_PRECEDING_LINES: usize = 3;
7
8pub(crate) fn has_preceding_comment(lines: &[&str], line: usize, keywords: &[&str]) -> bool {
10 if line <= 1 {
11 return false;
12 }
13
14 let idx = line - 1;
15 let start = idx.saturating_sub(MAX_PRECEDING_LINES);
16
17 for i in (start..idx).rev() {
18 let Some(raw) = lines.get(i) else {
19 break;
20 };
21 let trimmed = raw.trim();
22
23 if trimmed.is_empty() {
24 continue;
25 }
26
27 if !parse::is_comment(trimmed) {
28 break;
29 }
30
31 if keywords.iter().any(|kw| trimmed.contains(kw)) {
32 return true;
33 }
34 }
35
36 false
37}
38
39pub(crate) fn contains_outside_strings(line: &str, pattern: &str) -> bool {
41 super::scanner::code_only(line).contains(pattern)
42}
43
44#[cfg(test)]
45mod tests {
46 use super::*;
47
48 #[gtest]
49 #[expect(
50 clippy::unnecessary_safety_comment,
51 reason = "the adjacent safety comment is the fixture under test"
52 )]
53 fn preceding_comment_found() -> Result<()> {
54 let lines = vec!["// SAFETY: valid pointer", "unsafe { }"];
55
56 verify_true!(has_preceding_comment(&lines, 2, &["SAFETY:"]))?;
57
58 Ok(())
59 }
60
61 #[gtest]
62 fn preceding_comment_not_found() -> Result<()> {
63 let lines = vec!["let x = 1;", "unsafe { }"];
64
65 verify_false!(has_preceding_comment(&lines, 2, &["SAFETY:"]))?;
66
67 Ok(())
68 }
69
70 #[gtest]
71 #[expect(
72 clippy::unnecessary_safety_comment,
73 reason = "the blank-separated safety comment is the fixture under test"
74 )]
75 fn preceding_comment_skips_blanks() -> Result<()> {
76 let lines = vec!["// SAFETY: ok", "", "unsafe { }"];
77
78 verify_true!(has_preceding_comment(&lines, 3, &["SAFETY:"]))?;
79
80 Ok(())
81 }
82
83 #[gtest]
84 fn preceding_comment_multiple_keywords() -> Result<()> {
85 let lines = vec!["// LEAK: intentional", "mem::forget(x);"];
86
87 verify_true!(has_preceding_comment(&lines, 2, &["LEAK:", "SAFETY:"]))?;
88
89 Ok(())
90 }
91
92 #[gtest]
93 #[expect(
94 clippy::unnecessary_safety_comment,
95 reason = "the interrupted safety comment is the fixture under test"
96 )]
97 fn preceding_comment_stops_at_code() -> Result<()> {
98 let lines = vec!["// SAFETY: ok", "let x = 1;", "unsafe { }"];
99
100 verify_false!(has_preceding_comment(&lines, 3, &["SAFETY:"]))?;
101
102 Ok(())
103 }
104
105 #[gtest]
106 fn line_one_returns_false() -> Result<()> {
107 let lines = vec!["unsafe { }"];
108
109 verify_false!(has_preceding_comment(&lines, 1, &["SAFETY:"]))?;
110
111 Ok(())
112 }
113
114 #[gtest]
115 fn preceding_comment_ignores_code_with_keyword() -> Result<()> {
116 let lines = vec!["let safety = \"SAFETY: nope\";", "unsafe { }"];
117
118 verify_false!(has_preceding_comment(&lines, 2, &["SAFETY:"]))?;
119
120 Ok(())
121 }
122
123 #[gtest]
124 fn outside_strings_in_code() -> Result<()> {
125 verify_true!(contains_outside_strings(
126 "static mut X: i32 = 0;",
127 "static mut "
128 ))?;
129
130 Ok(())
131 }
132
133 #[gtest]
134 fn outside_strings_in_string_literal() -> Result<()> {
135 verify_false!(contains_outside_strings(
136 r#"let msg = "static mut is dangerous";"#,
137 "static mut "
138 ))?;
139
140 Ok(())
141 }
142
143 #[gtest]
144 fn outside_strings_in_comment() -> Result<()> {
145 verify_false!(contains_outside_strings(
146 "// static mut X: i32 = 0;",
147 "static mut "
148 ))?;
149
150 Ok(())
151 }
152
153 #[gtest]
154 fn outside_strings_mixed() -> Result<()> {
155 verify_true!(contains_outside_strings(
156 r#"let msg = "hello"; static mut X: i32 = 0;"#,
157 "static mut "
158 ))?;
159
160 Ok(())
161 }
162
163 #[gtest]
164 fn outside_strings_char_literal() -> Result<()> {
165 verify_false!(contains_outside_strings("let c = '{'; // no match", "{;"))?;
166
167 Ok(())
168 }
169}