1use ryo_source::pure::{PureBlock, PureExpr, PureFn, PureStmt};
7
8use super::marker::{DebugMarker, MARKER_PREFIX};
9use crate::Mutation;
10
11#[derive(Debug, Clone)]
13pub enum RemovalTarget {
14 All,
16 BySession(String),
18 OlderThan(u64),
20 ByDescription(String),
22}
23
24#[derive(Debug, Clone)]
41pub struct RemoveDebugLogsMutation {
42 pub target: RemovalTarget,
44}
45
46impl RemoveDebugLogsMutation {
47 pub fn all() -> Self {
49 Self {
50 target: RemovalTarget::All,
51 }
52 }
53
54 pub fn by_session(session_id: impl Into<String>) -> Self {
56 Self {
57 target: RemovalTarget::BySession(session_id.into()),
58 }
59 }
60
61 pub fn older_than(timestamp: u64) -> Self {
63 Self {
64 target: RemovalTarget::OlderThan(timestamp),
65 }
66 }
67
68 pub fn by_description(pattern: impl Into<String>) -> Self {
70 Self {
71 target: RemovalTarget::ByDescription(pattern.into()),
72 }
73 }
74
75 fn should_remove(&self, marker: &DebugMarker) -> bool {
77 match &self.target {
78 RemovalTarget::All => true,
79 RemovalTarget::BySession(session) => &marker.session_id == session,
80 RemovalTarget::OlderThan(ts) => marker.timestamp < *ts,
81 RemovalTarget::ByDescription(pattern) => marker
82 .description
83 .as_ref()
84 .map(|d| d.contains(pattern))
85 .unwrap_or(false),
86 }
87 }
88
89 fn has_removable_marker(&self, expr: &PureExpr) -> bool {
91 match expr {
92 PureExpr::Macro { name, tokens, .. } => {
93 if name == "dbg" && DebugMarker::contains_marker(tokens) {
94 if let Some(marker) = self.extract_marker_from_tokens(tokens) {
96 return self.should_remove(&marker);
97 }
98 }
99 false
100 }
101 _ => false,
102 }
103 }
104
105 fn block_has_removable_marker(&self, expr: &PureExpr) -> bool {
107 match expr {
108 PureExpr::Block { block, .. } => block.stmts.iter().any(|stmt| match stmt {
109 PureStmt::Semi(e) | PureStmt::Expr(e) => self.has_removable_marker(e),
110 _ => false,
111 }),
112 PureExpr::Macro { name, tokens, .. } => {
113 name == "dbg" && DebugMarker::contains_marker(tokens) && {
114 self.extract_marker_from_tokens(tokens)
115 .map(|m| self.should_remove(&m))
116 .unwrap_or(false)
117 }
118 }
119 _ => false,
120 }
121 }
122
123 fn extract_marker_from_tokens(&self, tokens: &str) -> Option<DebugMarker> {
125 let string_prefix = format!("\"{}:", MARKER_PREFIX);
127 if let Some(start) = tokens.find(&string_prefix) {
128 let rest = &tokens[start + 1..]; if let Some(end) = rest.find('"') {
131 let marker_content = &rest[..end];
132 return DebugMarker::from_comment(marker_content);
133 }
134 }
135
136 let comment_prefix = format!("/* {}:", MARKER_PREFIX);
138 if let Some(start) = tokens.find(&comment_prefix) {
139 if let Some(end_offset) = tokens[start..].find("*/") {
140 let end = start + end_offset + 2;
141 return DebugMarker::from_comment(&tokens[start..end]);
142 }
143 }
144
145 None
146 }
147
148 fn extract_dbg_inner(&self, tokens: &str) -> Option<String> {
150 let string_prefix = format!("\"{}:", MARKER_PREFIX);
155 if let Some(start) = tokens.find(&string_prefix) {
156 let rest = &tokens[start + 1..]; if let Some(quote_end) = rest.find('"') {
158 let after_marker = &rest[quote_end + 1..].trim_start();
160 if let Some(after_comma) = after_marker.strip_prefix(',') {
161 let expr = after_comma.trim();
162 if !expr.is_empty() {
163 return Some(expr.to_string());
164 }
165 }
166 }
167 }
168
169 let comment_prefix = format!("/* {}:", MARKER_PREFIX);
171 if let Some(start) = tokens.find(&comment_prefix) {
172 if let Some(end_offset) = tokens[start..].find("*/") {
173 let end = start + end_offset + 2;
174 let rest = tokens[end..].trim();
175 if !rest.is_empty() {
176 return Some(rest.to_string());
177 }
178 }
179 }
180
181 None
182 }
183
184 fn transform_expr(&self, expr: &PureExpr) -> (PureExpr, usize) {
186 match expr {
187 PureExpr::Macro { name, tokens, .. } if name == "dbg" => {
189 if self.has_removable_marker(expr) {
190 if let Some(inner) = self.extract_dbg_inner(tokens) {
192 return (PureExpr::Other(inner), 1);
194 }
195 }
196 (expr.clone(), 0)
197 }
198
199 PureExpr::MethodCall {
201 receiver,
202 method,
203 args,
204 ..
205 } => {
206 let (new_receiver, mut count) = self.transform_expr(receiver);
208
209 if method == "inspect" && args.len() == 1 {
211 if let PureExpr::Closure { body, .. } = &args[0] {
212 if self.block_has_removable_marker(body) {
213 return (new_receiver, count + 1);
215 }
216 }
217 }
218
219 let new_args: Vec<_> = args
221 .iter()
222 .map(|a| {
223 let (new_a, c) = self.transform_expr(a);
224 count += c;
225 new_a
226 })
227 .collect();
228
229 (
230 PureExpr::MethodCall {
231 receiver: Box::new(new_receiver),
232 method: method.clone(),
233 turbofish: None,
234 args: new_args,
235 },
236 count,
237 )
238 }
239
240 PureExpr::Call { func, args } => {
242 let (new_func, mut count) = self.transform_expr(func);
243 let new_args: Vec<_> = args
244 .iter()
245 .map(|a| {
246 let (new_a, c) = self.transform_expr(a);
247 count += c;
248 new_a
249 })
250 .collect();
251 (
252 PureExpr::Call {
253 func: Box::new(new_func),
254 args: new_args,
255 },
256 count,
257 )
258 }
259
260 PureExpr::Binary { op, left, right } => {
261 let (new_left, c1) = self.transform_expr(left);
262 let (new_right, c2) = self.transform_expr(right);
263 (
264 PureExpr::Binary {
265 op: op.clone(),
266 left: Box::new(new_left),
267 right: Box::new(new_right),
268 },
269 c1 + c2,
270 )
271 }
272
273 PureExpr::Block { label, block } => {
274 let (new_block, count) = self.transform_block(block);
275 (
276 PureExpr::Block {
277 label: label.clone(),
278 block: new_block,
279 },
280 count,
281 )
282 }
283
284 PureExpr::If {
285 cond,
286 then_branch,
287 else_branch,
288 } => {
289 let (new_cond, c1) = self.transform_expr(cond);
290 let (new_then, c2) = self.transform_block(then_branch);
291 let (new_else, c3) = if let Some(e) = else_branch {
292 let (ne, c) = self.transform_expr(e);
293 (Some(Box::new(ne)), c)
294 } else {
295 (None, 0)
296 };
297 (
298 PureExpr::If {
299 cond: Box::new(new_cond),
300 then_branch: new_then,
301 else_branch: new_else,
302 },
303 c1 + c2 + c3,
304 )
305 }
306
307 PureExpr::Closure {
308 params, ret, body, ..
309 } => {
310 let (new_body, count) = self.transform_expr(body);
311 (
312 PureExpr::Closure {
313 is_async: false,
314 is_move: false,
315 params: params.clone(),
316 ret: ret.clone(),
317 body: Box::new(new_body),
318 },
319 count,
320 )
321 }
322
323 _ => (expr.clone(), 0),
324 }
325 }
326
327 fn transform_block(&self, block: &PureBlock) -> (PureBlock, usize) {
329 let mut count = 0;
330 let new_stmts: Vec<_> = block
331 .stmts
332 .iter()
333 .map(|stmt| {
334 let (new_stmt, c) = self.transform_stmt(stmt);
335 count += c;
336 new_stmt
337 })
338 .collect();
339 (PureBlock { stmts: new_stmts }, count)
340 }
341
342 fn transform_stmt(&self, stmt: &PureStmt) -> (PureStmt, usize) {
344 match stmt {
345 PureStmt::Local { pattern, ty, init } => {
346 if let Some(init_expr) = init {
347 let (new_init, count) = self.transform_expr(init_expr);
348 (
349 PureStmt::Local {
350 pattern: pattern.clone(),
351 ty: ty.clone(),
352 init: Some(new_init),
353 },
354 count,
355 )
356 } else {
357 (stmt.clone(), 0)
358 }
359 }
360 PureStmt::Expr(expr) => {
361 let (new_expr, count) = self.transform_expr(expr);
362 (PureStmt::Expr(new_expr), count)
363 }
364 PureStmt::Semi(expr) => {
365 let (new_expr, count) = self.transform_expr(expr);
366 (PureStmt::Semi(new_expr), count)
367 }
368 _ => (stmt.clone(), 0),
369 }
370 }
371
372 pub fn transform_fn(&self, func: &PureFn) -> (PureFn, usize) {
374 let (new_body, count) = self.transform_block(&func.body);
375 let mut new_func = func.clone();
376 new_func.body = new_body;
377 (new_func, count)
378 }
379}
380
381impl Mutation for RemoveDebugLogsMutation {
382 fn describe(&self) -> String {
383 match &self.target {
384 RemovalTarget::All => "Remove all ryo debug logs".to_string(),
385 RemovalTarget::BySession(s) => format!("Remove debug logs from session '{}'", s),
386 RemovalTarget::OlderThan(ts) => format!("Remove debug logs older than {}", ts),
387 RemovalTarget::ByDescription(p) => format!("Remove debug logs matching '{}'", p),
388 }
389 }
390
391 fn mutation_type(&self) -> &'static str {
392 "RemoveDebugLogs"
393 }
394
395 fn box_clone(&self) -> Box<dyn Mutation> {
396 Box::new(self.clone())
397 }
398}