Skip to main content

wowlab_tidy/infra/
helpers.rs

1#[cfg(test)]
2use googletest::prelude::*;
3
4use super::parse;
5
6const MAX_PRECEDING_LINES: usize = 3;
7
8/// Check whether any `keyword` appears in the comment block immediately preceding `line`.
9pub(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
39/// Check if `pattern` appears in `line` outside of string literals and comments.
40pub(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}