ryo-mutations 0.2.0

[experimental] Code transformation primitives for Rust source code
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
//! Remove debug logs inserted by ryo.
//!
//! This mutation removes debug logs that were previously inserted,
//! using the embedded markers to identify them.

use ryo_source::pure::{PureBlock, PureExpr, PureFn, PureStmt};

use super::marker::{DebugMarker, MARKER_PREFIX};
use crate::Mutation;

/// Target for removal.
#[derive(Debug, Clone)]
pub enum RemovalTarget {
    /// Remove all ryo-inserted debug logs.
    All,
    /// Remove logs from a specific session.
    BySession(String),
    /// Remove logs older than a timestamp.
    OlderThan(u64),
    /// Remove logs matching a description pattern.
    ByDescription(String),
}

/// Remove debug logs inserted by ryo.
///
/// # Example
///
/// ```ignore
/// use ryo_mutations::debugger::{RemoveDebugLogsMutation, RemovalTarget};
///
/// // Remove all debug logs
/// let mutation = RemoveDebugLogsMutation::all();
///
/// // Remove logs from a specific session
/// let mutation = RemoveDebugLogsMutation::by_session("abc123");
///
/// // Remove logs older than an hour ago
/// let mutation = RemoveDebugLogsMutation::older_than(timestamp);
/// ```
#[derive(Debug, Clone)]
pub struct RemoveDebugLogsMutation {
    /// What to remove.
    pub target: RemovalTarget,
}

impl RemoveDebugLogsMutation {
    /// Remove all ryo-inserted debug logs.
    pub fn all() -> Self {
        Self {
            target: RemovalTarget::All,
        }
    }

    /// Remove logs from a specific session.
    pub fn by_session(session_id: impl Into<String>) -> Self {
        Self {
            target: RemovalTarget::BySession(session_id.into()),
        }
    }

    /// Remove logs older than a timestamp.
    pub fn older_than(timestamp: u64) -> Self {
        Self {
            target: RemovalTarget::OlderThan(timestamp),
        }
    }

    /// Remove logs matching a description pattern.
    pub fn by_description(pattern: impl Into<String>) -> Self {
        Self {
            target: RemovalTarget::ByDescription(pattern.into()),
        }
    }

    /// Check if a marker should be removed.
    fn should_remove(&self, marker: &DebugMarker) -> bool {
        match &self.target {
            RemovalTarget::All => true,
            RemovalTarget::BySession(session) => &marker.session_id == session,
            RemovalTarget::OlderThan(ts) => marker.timestamp < *ts,
            RemovalTarget::ByDescription(pattern) => marker
                .description
                .as_ref()
                .map(|d| d.contains(pattern))
                .unwrap_or(false),
        }
    }

    /// Check if an expression contains a ryo debug marker that should be removed.
    fn has_removable_marker(&self, expr: &PureExpr) -> bool {
        match expr {
            PureExpr::Macro { name, tokens, .. } => {
                if name == "dbg" && DebugMarker::contains_marker(tokens) {
                    // Parse the marker and check if it should be removed
                    if let Some(marker) = self.extract_marker_from_tokens(tokens) {
                        return self.should_remove(&marker);
                    }
                }
                false
            }
            _ => false,
        }
    }

    /// Check if a block contains a removable marker.
    fn block_has_removable_marker(&self, expr: &PureExpr) -> bool {
        match expr {
            PureExpr::Block { block, .. } => block.stmts.iter().any(|stmt| match stmt {
                PureStmt::Semi(e) | PureStmt::Expr(e) => self.has_removable_marker(e),
                _ => false,
            }),
            PureExpr::Macro { name, tokens, .. } => {
                name == "dbg" && DebugMarker::contains_marker(tokens) && {
                    self.extract_marker_from_tokens(tokens)
                        .map(|m| self.should_remove(&m))
                        .unwrap_or(false)
                }
            }
            _ => false,
        }
    }

    /// Extract a marker from macro tokens.
    fn extract_marker_from_tokens(&self, tokens: &str) -> Option<DebugMarker> {
        // Try string literal format first: "ryo-debug:..."
        let string_prefix = format!("\"{}:", MARKER_PREFIX);
        if let Some(start) = tokens.find(&string_prefix) {
            // Find the closing quote
            let rest = &tokens[start + 1..]; // skip opening quote
            if let Some(end) = rest.find('"') {
                let marker_content = &rest[..end];
                return DebugMarker::from_comment(marker_content);
            }
        }

        // Try comment format: /* ryo-debug:... */
        let comment_prefix = format!("/* {}:", MARKER_PREFIX);
        if let Some(start) = tokens.find(&comment_prefix) {
            if let Some(end_offset) = tokens[start..].find("*/") {
                let end = start + end_offset + 2;
                return DebugMarker::from_comment(&tokens[start..end]);
            }
        }

        None
    }

    /// Extract the inner expression from a dbg! macro.
    fn extract_dbg_inner(&self, tokens: &str) -> Option<String> {
        // Format is: "ryo-debug:...", expression
        // We need to extract the expression part after the comma

        // Try string literal format first
        let string_prefix = format!("\"{}:", MARKER_PREFIX);
        if let Some(start) = tokens.find(&string_prefix) {
            let rest = &tokens[start + 1..]; // skip opening quote
            if let Some(quote_end) = rest.find('"') {
                // Skip past the closing quote and comma
                let after_marker = &rest[quote_end + 1..].trim_start();
                if let Some(after_comma) = after_marker.strip_prefix(',') {
                    let expr = after_comma.trim();
                    if !expr.is_empty() {
                        return Some(expr.to_string());
                    }
                }
            }
        }

        // Try comment format: /* ryo-debug:... */ expression
        let comment_prefix = format!("/* {}:", MARKER_PREFIX);
        if let Some(start) = tokens.find(&comment_prefix) {
            if let Some(end_offset) = tokens[start..].find("*/") {
                let end = start + end_offset + 2;
                let rest = tokens[end..].trim();
                if !rest.is_empty() {
                    return Some(rest.to_string());
                }
            }
        }

        None
    }

    /// Transform an expression, removing debug logs.
    fn transform_expr(&self, expr: &PureExpr) -> (PureExpr, usize) {
        match expr {
            // Handle dbg! macros
            PureExpr::Macro { name, tokens, .. } if name == "dbg" => {
                if self.has_removable_marker(expr) {
                    // Try to extract the inner expression
                    if let Some(inner) = self.extract_dbg_inner(tokens) {
                        // Parse the inner expression (simplified - just return as Other)
                        return (PureExpr::Other(inner), 1);
                    }
                }
                (expr.clone(), 0)
            }

            // Handle method chains with inspect
            PureExpr::MethodCall {
                receiver,
                method,
                args,
                ..
            } => {
                // First transform the receiver
                let (new_receiver, mut count) = self.transform_expr(receiver);

                // Check if this is a removable inspect
                if method == "inspect" && args.len() == 1 {
                    if let PureExpr::Closure { body, .. } = &args[0] {
                        if self.block_has_removable_marker(body) {
                            // Skip this inspect, return the receiver
                            return (new_receiver, count + 1);
                        }
                    }
                }

                // Transform args
                let new_args: Vec<_> = args
                    .iter()
                    .map(|a| {
                        let (new_a, c) = self.transform_expr(a);
                        count += c;
                        new_a
                    })
                    .collect();

                (
                    PureExpr::MethodCall {
                        receiver: Box::new(new_receiver),
                        method: method.clone(),
                        turbofish: None,
                        args: new_args,
                    },
                    count,
                )
            }

            // Recursively transform other expressions
            PureExpr::Call { func, args } => {
                let (new_func, mut count) = self.transform_expr(func);
                let new_args: Vec<_> = args
                    .iter()
                    .map(|a| {
                        let (new_a, c) = self.transform_expr(a);
                        count += c;
                        new_a
                    })
                    .collect();
                (
                    PureExpr::Call {
                        func: Box::new(new_func),
                        args: new_args,
                    },
                    count,
                )
            }

            PureExpr::Binary { op, left, right } => {
                let (new_left, c1) = self.transform_expr(left);
                let (new_right, c2) = self.transform_expr(right);
                (
                    PureExpr::Binary {
                        op: op.clone(),
                        left: Box::new(new_left),
                        right: Box::new(new_right),
                    },
                    c1 + c2,
                )
            }

            PureExpr::Block { label, block } => {
                let (new_block, count) = self.transform_block(block);
                (
                    PureExpr::Block {
                        label: label.clone(),
                        block: new_block,
                    },
                    count,
                )
            }

            PureExpr::If {
                cond,
                then_branch,
                else_branch,
            } => {
                let (new_cond, c1) = self.transform_expr(cond);
                let (new_then, c2) = self.transform_block(then_branch);
                let (new_else, c3) = if let Some(e) = else_branch {
                    let (ne, c) = self.transform_expr(e);
                    (Some(Box::new(ne)), c)
                } else {
                    (None, 0)
                };
                (
                    PureExpr::If {
                        cond: Box::new(new_cond),
                        then_branch: new_then,
                        else_branch: new_else,
                    },
                    c1 + c2 + c3,
                )
            }

            PureExpr::Closure {
                params, ret, body, ..
            } => {
                let (new_body, count) = self.transform_expr(body);
                (
                    PureExpr::Closure {
                        is_async: false,
                        is_move: false,
                        params: params.clone(),
                        ret: ret.clone(),
                        body: Box::new(new_body),
                    },
                    count,
                )
            }

            _ => (expr.clone(), 0),
        }
    }

    /// Transform a block.
    fn transform_block(&self, block: &PureBlock) -> (PureBlock, usize) {
        let mut count = 0;
        let new_stmts: Vec<_> = block
            .stmts
            .iter()
            .map(|stmt| {
                let (new_stmt, c) = self.transform_stmt(stmt);
                count += c;
                new_stmt
            })
            .collect();
        (PureBlock { stmts: new_stmts }, count)
    }

    /// Transform a statement.
    fn transform_stmt(&self, stmt: &PureStmt) -> (PureStmt, usize) {
        match stmt {
            PureStmt::Local {
                pattern,
                ty,
                init,
                else_branch,
            } => {
                if let Some(init_expr) = init {
                    let (new_init, count) = self.transform_expr(init_expr);
                    (
                        PureStmt::Local {
                            pattern: pattern.clone(),
                            ty: ty.clone(),
                            init: Some(new_init),
                            else_branch: else_branch.clone(),
                        },
                        count,
                    )
                } else {
                    (stmt.clone(), 0)
                }
            }
            PureStmt::Expr(expr) => {
                let (new_expr, count) = self.transform_expr(expr);
                (PureStmt::Expr(new_expr), count)
            }
            PureStmt::Semi(expr) => {
                let (new_expr, count) = self.transform_expr(expr);
                (PureStmt::Semi(new_expr), count)
            }
            _ => (stmt.clone(), 0),
        }
    }

    /// Transform a function.
    pub fn transform_fn(&self, func: &PureFn) -> (PureFn, usize) {
        let (new_body, count) = self.transform_block(&func.body);
        let mut new_func = func.clone();
        new_func.body = new_body;
        (new_func, count)
    }
}

impl Mutation for RemoveDebugLogsMutation {
    fn describe(&self) -> String {
        match &self.target {
            RemovalTarget::All => "Remove all ryo debug logs".to_string(),
            RemovalTarget::BySession(s) => format!("Remove debug logs from session '{}'", s),
            RemovalTarget::OlderThan(ts) => format!("Remove debug logs older than {}", ts),
            RemovalTarget::ByDescription(p) => format!("Remove debug logs matching '{}'", p),
        }
    }

    fn mutation_type(&self) -> &'static str {
        "RemoveDebugLogs"
    }

    fn box_clone(&self) -> Box<dyn Mutation> {
        Box::new(self.clone())
    }
}