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 comment_pattern = Regex::new(r"^\s*//")?;
65
66 let entries: Vec<_> = if path.is_file() {
67 vec![path.to_path_buf()]
68 } else {
69 WalkDir::new(path)
70 .into_iter()
71 .filter_map(|e| e.ok())
72 .filter(|e| {
73 e.path().is_file()
74 && e.path()
75 .extension()
76 .and_then(|s| s.to_str())
77 .map(|ext| {
78 matches!(
79 ext,
80 "c" | "h" | "cpp" | "hpp" | "cc" | "cxx" | "rs" | "java"
81 )
82 })
83 .unwrap_or(false)
84 })
85 .map(|e| e.path().to_path_buf())
86 .collect()
87 };
88
89 for file_path in entries {
90 let content = fs::read_to_string(&file_path)
91 .with_context(|| format!("Failed to read file: {}", file_path.display()))?;
92
93 for cap in c_functions_pattern.find_iter(&content) {
95 let match_str = cap.as_str();
96 let start_offset = cap.start();
97 let end_offset = cap.end();
98
99 let line_number = content[..start_offset].matches('\n').count() + 1;
102 let end_line_number = content[..end_offset].matches('\n').count() + 1;
104
105 let line_start_offset = content[..start_offset]
107 .rfind('\n')
108 .map(|pos| pos + 1)
109 .unwrap_or(0);
110 let line_content = content[line_start_offset..]
111 .lines()
112 .next()
113 .unwrap_or("")
114 .to_string();
115
116 let is_commented = comment_pattern.is_match(&line_content);
118
119 if is_commented == find_commented {
120 let multiline_content: Vec<String> =
122 match_str.lines().map(|s| s.to_string()).collect();
123
124 matches.push(Match {
125 file_path: file_path.clone(),
126 line_number,
127 end_line_number,
128 line_content: match_str.replace('\n', " ").trim().to_string(),
129 multiline_content,
130 });
131 }
132 }
133
134 for cap in cpp_stream_pattern.find_iter(&content) {
136 let match_str = cap.as_str();
137 let start_offset = cap.start();
138 let end_offset = cap.end();
139
140 let line_number = content[..start_offset].matches('\n').count() + 1;
143 let end_line_number = content[..end_offset].matches('\n').count() + 1;
145
146 let line_start_offset = content[..start_offset]
148 .rfind('\n')
149 .map(|pos| pos + 1)
150 .unwrap_or(0);
151 let line_content = content[line_start_offset..]
152 .lines()
153 .next()
154 .unwrap_or("")
155 .to_string();
156
157 let is_commented = comment_pattern.is_match(&line_content);
159
160 if is_commented == find_commented {
161 let multiline_content: Vec<String> =
163 match_str.lines().map(|s| s.to_string()).collect();
164
165 matches.push(Match {
166 file_path: file_path.clone(),
167 line_number,
168 end_line_number,
169 line_content: match_str.replace('\n', " ").trim().to_string(),
170 multiline_content,
171 });
172 }
173 }
174
175 for cap in rust_macro_pattern.find_iter(&content) {
177 let match_str = cap.as_str();
178 let start_offset = cap.start();
179 let end_offset = cap.end();
180
181 let line_number = content[..start_offset].matches('\n').count() + 1;
183 let end_line_number = content[..end_offset].matches('\n').count() + 1;
184
185 let line_start_offset = content[..start_offset]
187 .rfind('\n')
188 .map(|pos| pos + 1)
189 .unwrap_or(0);
190 let line_content = content[line_start_offset..]
191 .lines()
192 .next()
193 .unwrap_or("")
194 .to_string();
195
196 let is_commented = comment_pattern.is_match(&line_content);
198
199 if is_commented == find_commented {
200 let multiline_content: Vec<String> =
202 match_str.lines().map(|s| s.to_string()).collect();
203
204 matches.push(Match {
205 file_path: file_path.clone(),
206 line_number,
207 end_line_number,
208 line_content: match_str.replace('\n', " ").trim().to_string(),
209 multiline_content,
210 });
211 }
212 }
213
214 for cap in java_pattern.find_iter(&content) {
216 let match_str = cap.as_str();
217 let start_offset = cap.start();
218 let end_offset = cap.end();
219
220 let line_number = content[..start_offset].matches('\n').count() + 1;
222 let end_line_number = content[..end_offset].matches('\n').count() + 1;
223
224 let line_start_offset = content[..start_offset]
226 .rfind('\n')
227 .map(|pos| pos + 1)
228 .unwrap_or(0);
229 let line_content = content[line_start_offset..]
230 .lines()
231 .next()
232 .unwrap_or("")
233 .to_string();
234
235 let is_commented = comment_pattern.is_match(&line_content);
237
238 if is_commented == find_commented {
239 let multiline_content: Vec<String> =
241 match_str.lines().map(|s| s.to_string()).collect();
242
243 matches.push(Match {
244 file_path: file_path.clone(),
245 line_number,
246 end_line_number,
247 line_content: match_str.replace('\n', " ").trim().to_string(),
248 multiline_content,
249 });
250 }
251 }
252 }
253
254 let mut seen = std::collections::HashSet::new();
257 matches.retain(|m| seen.insert((m.file_path.clone(), m.line_number)));
258
259 Ok(matches)
260}