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 comment_pattern = Regex::new(r"^\s*//")?;
40
41 let entries: Vec<_> = if path.is_file() {
42 vec![path.to_path_buf()]
43 } else {
44 WalkDir::new(path)
45 .into_iter()
46 .filter_map(|e| e.ok())
47 .filter(|e| {
48 e.path().is_file()
49 && e.path()
50 .extension()
51 .and_then(|s| s.to_str())
52 .map(|ext| matches!(ext, "c" | "h" | "cpp" | "hpp" | "cc" | "cxx"))
53 .unwrap_or(false)
54 })
55 .map(|e| e.path().to_path_buf())
56 .collect()
57 };
58
59 for file_path in entries {
60 let content = fs::read_to_string(&file_path)
61 .with_context(|| format!("Failed to read file: {}", file_path.display()))?;
62
63 for cap in c_functions_pattern.find_iter(&content) {
65 let match_str = cap.as_str();
66 let start_offset = cap.start();
67 let end_offset = cap.end();
68
69 let line_number = content[..start_offset].matches('\n').count() + 1;
72 let end_line_number = content[..end_offset].matches('\n').count() + 1;
74
75 let line_start_offset = content[..start_offset]
77 .rfind('\n')
78 .map(|pos| pos + 1)
79 .unwrap_or(0);
80 let line_content = content[line_start_offset..]
81 .lines()
82 .next()
83 .unwrap_or("")
84 .to_string();
85
86 let is_commented = comment_pattern.is_match(&line_content);
88
89 if is_commented == find_commented {
90 let multiline_content: Vec<String> = match_str.lines().map(|s| s.to_string()).collect();
92
93 matches.push(Match {
94 file_path: file_path.clone(),
95 line_number,
96 end_line_number,
97 line_content: match_str.replace('\n', " ").trim().to_string(),
98 multiline_content,
99 });
100 }
101 }
102
103 for cap in cpp_stream_pattern.find_iter(&content) {
105 let match_str = cap.as_str();
106 let start_offset = cap.start();
107 let end_offset = cap.end();
108
109 let line_number = content[..start_offset].matches('\n').count() + 1;
112 let end_line_number = content[..end_offset].matches('\n').count() + 1;
114
115 let line_start_offset = content[..start_offset]
117 .rfind('\n')
118 .map(|pos| pos + 1)
119 .unwrap_or(0);
120 let line_content = content[line_start_offset..]
121 .lines()
122 .next()
123 .unwrap_or("")
124 .to_string();
125
126 let is_commented = comment_pattern.is_match(&line_content);
128
129 if is_commented == find_commented {
130 let multiline_content: Vec<String> = match_str.lines().map(|s| s.to_string()).collect();
132
133 matches.push(Match {
134 file_path: file_path.clone(),
135 line_number,
136 end_line_number,
137 line_content: match_str.replace('\n', " ").trim().to_string(),
138 multiline_content,
139 });
140 }
141 }
142 }
143
144 Ok(matches)
145}