1use anyhow::{Context, Result};
2use regex::Regex;
3use std::fs;
4use std::path::Path;
5use walkdir::WalkDir;
6
7use crate::types::Match;
8
9pub fn find_debug_printfs(
10 path: &Path,
11 find_commented: bool,
12 detect_all: bool,
13) -> Result<Vec<Match>> {
14 let mut matches = Vec::new();
15
16 let c_functions_pattern = if detect_all {
19 Regex::new(
21 r"(?s)(printf|fprintf|sprintf|snprintf|printf_debug|dprintf|puts|fputs|fputc|putchar|fputchar|write|perror)\s*\([^)]*\)\s*;",
22 )?
23 } else {
24 Regex::new(
26 r"(?s)(printf|fprintf|sprintf|snprintf|printf_debug|dprintf|puts|fputs|fputc|putchar|fputchar|write|perror)\s*\([^)]*?(debug|DEBUG)[^)]*\)\s*;",
27 )?
28 };
29
30 let cpp_stream_pattern = if detect_all {
32 Regex::new(r"(?s)(std::cout|std::cerr|std::clog)\s*<<[^;]*?;")?
34 } else {
35 Regex::new(r"(?s)(std::cout|std::cerr|std::clog)\s*<<[^;]*?(debug|DEBUG)[^;]*?;")?
37 };
38
39 let rust_macro_pattern = if detect_all {
42 Regex::new(r"(?s)(println!|eprintln!|print!|eprint!|dbg!)\s*\([^)]*\)\s*;")?
44 } else {
45 Regex::new(
47 r"(?s)dbg!\s*\([^)]*\)\s*;|(println!|eprintln!|print!|eprint!)\s*\([^)]*?(debug|DEBUG)[^)]*\)\s*;",
48 )?
49 };
50
51 let java_pattern = if detect_all {
53 Regex::new(
55 r"(?s)(System\.out\.println|System\.out\.printf|System\.out\.print|System\.err\.println|System\.err\.printf|System\.err\.print)\s*\([^)]*\)\s*;",
56 )?
57 } else {
58 Regex::new(
60 r"(?s)(System\.out\.println|System\.out\.printf|System\.out\.print|System\.err\.println|System\.err\.printf|System\.err\.print)\s*\([^)]*?(debug|DEBUG)[^)]*\)\s*;",
61 )?
62 };
63
64 let go_pattern = if detect_all {
67 Regex::new(
69 r"(?s)(fmt\.Println|fmt\.Printf|fmt\.Print|fmt\.Fprintln|fmt\.Fprintf|fmt\.Fprint|log\.Println|fmt\.Printf|log\.Print|log\.Fatal|log\.Fatalf|log\.Fatalln|log\.Panic|log\.Panicf|log\.Panicln)\s*\([^)]*\)",
70 )?
71 } else {
72 Regex::new(
74 r"(?s)(fmt\.Println|fmt\.Printf|fmt\.Print|fmt\.Fprintln|fmt\.Fprintf|fmt\.Fprint|log\.Println|log\.Printf|log\.Print|log\.Fatal|log\.Fatalf|log\.Fatalln|log\.Panic|log\.Panicf|log\.Panicln)\s*\([^)]*?(debug|DEBUG)[^)]*\)",
75 )?
76 };
77
78 let comment_pattern = Regex::new(r"^\s*//")?;
79
80 let entries: Vec<_> = if path.is_file() {
81 vec![path.to_path_buf()]
82 } else {
83 WalkDir::new(path)
84 .into_iter()
85 .filter_map(|e| e.ok())
86 .filter(|e| {
87 e.path().is_file()
88 && e.path()
89 .extension()
90 .and_then(|s| s.to_str())
91 .map(|ext| {
92 matches!(
93 ext,
94 "c" | "h" | "cpp" | "hpp" | "cc" | "cxx" | "rs" | "java" | "go"
95 )
96 })
97 .unwrap_or(false)
98 })
99 .map(|e| e.path().to_path_buf())
100 .collect()
101 };
102
103 for file_path in entries {
104 let content = fs::read_to_string(&file_path)
105 .with_context(|| format!("Failed to read file: {}", file_path.display()))?;
106
107 for cap in c_functions_pattern.find_iter(&content) {
109 let match_str = cap.as_str();
110 let start_offset = cap.start();
111 let end_offset = cap.end();
112
113 let line_number = content[..start_offset].matches('\n').count() + 1;
116 let end_line_number = content[..end_offset].matches('\n').count() + 1;
118
119 let line_start_offset = content[..start_offset]
121 .rfind('\n')
122 .map(|pos| pos + 1)
123 .unwrap_or(0);
124 let line_content = content[line_start_offset..]
125 .lines()
126 .next()
127 .unwrap_or("")
128 .to_string();
129
130 let is_commented = comment_pattern.is_match(&line_content);
132
133 if is_commented == find_commented {
134 let multiline_content: Vec<String> =
136 match_str.lines().map(|s| s.to_string()).collect();
137
138 matches.push(Match {
139 file_path: file_path.clone(),
140 line_number,
141 end_line_number,
142 line_content: match_str.replace('\n', " ").trim().to_string(),
143 multiline_content,
144 });
145 }
146 }
147
148 for cap in cpp_stream_pattern.find_iter(&content) {
150 let match_str = cap.as_str();
151 let start_offset = cap.start();
152 let end_offset = cap.end();
153
154 let line_number = content[..start_offset].matches('\n').count() + 1;
157 let end_line_number = content[..end_offset].matches('\n').count() + 1;
159
160 let line_start_offset = content[..start_offset]
162 .rfind('\n')
163 .map(|pos| pos + 1)
164 .unwrap_or(0);
165 let line_content = content[line_start_offset..]
166 .lines()
167 .next()
168 .unwrap_or("")
169 .to_string();
170
171 let is_commented = comment_pattern.is_match(&line_content);
173
174 if is_commented == find_commented {
175 let multiline_content: Vec<String> =
177 match_str.lines().map(|s| s.to_string()).collect();
178
179 matches.push(Match {
180 file_path: file_path.clone(),
181 line_number,
182 end_line_number,
183 line_content: match_str.replace('\n', " ").trim().to_string(),
184 multiline_content,
185 });
186 }
187 }
188
189 for cap in rust_macro_pattern.find_iter(&content) {
191 let match_str = cap.as_str();
192 let start_offset = cap.start();
193 let end_offset = cap.end();
194
195 let line_number = content[..start_offset].matches('\n').count() + 1;
197 let end_line_number = content[..end_offset].matches('\n').count() + 1;
198
199 let line_start_offset = content[..start_offset]
201 .rfind('\n')
202 .map(|pos| pos + 1)
203 .unwrap_or(0);
204 let line_content = content[line_start_offset..]
205 .lines()
206 .next()
207 .unwrap_or("")
208 .to_string();
209
210 let is_commented = comment_pattern.is_match(&line_content);
212
213 if is_commented == find_commented {
214 let multiline_content: Vec<String> =
216 match_str.lines().map(|s| s.to_string()).collect();
217
218 matches.push(Match {
219 file_path: file_path.clone(),
220 line_number,
221 end_line_number,
222 line_content: match_str.replace('\n', " ").trim().to_string(),
223 multiline_content,
224 });
225 }
226 }
227
228 for cap in java_pattern.find_iter(&content) {
230 let match_str = cap.as_str();
231 let start_offset = cap.start();
232 let end_offset = cap.end();
233
234 let line_number = content[..start_offset].matches('\n').count() + 1;
236 let end_line_number = content[..end_offset].matches('\n').count() + 1;
237
238 let line_start_offset = content[..start_offset]
240 .rfind('\n')
241 .map(|pos| pos + 1)
242 .unwrap_or(0);
243 let line_content = content[line_start_offset..]
244 .lines()
245 .next()
246 .unwrap_or("")
247 .to_string();
248
249 let is_commented = comment_pattern.is_match(&line_content);
251
252 if is_commented == find_commented {
253 let multiline_content: Vec<String> =
255 match_str.lines().map(|s| s.to_string()).collect();
256
257 matches.push(Match {
258 file_path: file_path.clone(),
259 line_number,
260 end_line_number,
261 line_content: match_str.replace('\n', " ").trim().to_string(),
262 multiline_content,
263 });
264 }
265 }
266
267 for cap in go_pattern.find_iter(&content) {
269 let match_str = cap.as_str();
270 let start_offset = cap.start();
271 let end_offset = cap.end();
272
273 let line_number = content[..start_offset].matches('\n').count() + 1;
275 let end_line_number = content[..end_offset].matches('\n').count() + 1;
276
277 let line_start_offset = content[..start_offset]
279 .rfind('\n')
280 .map(|pos| pos + 1)
281 .unwrap_or(0);
282 let line_content = content[line_start_offset..]
283 .lines()
284 .next()
285 .unwrap_or("")
286 .to_string();
287
288 let is_commented = comment_pattern.is_match(&line_content);
290
291 if is_commented == find_commented {
292 let multiline_content: Vec<String> =
294 match_str.lines().map(|s| s.to_string()).collect();
295
296 matches.push(Match {
297 file_path: file_path.clone(),
298 line_number,
299 end_line_number,
300 line_content: match_str.replace('\n', " ").trim().to_string(),
301 multiline_content,
302 });
303 }
304 }
305 }
306
307 let mut seen = std::collections::HashSet::new();
310 matches.retain(|m| seen.insert((m.file_path.clone(), m.line_number)));
311
312 Ok(matches)
313}