use ryo_source::pure::{PureBlock, PureExpr, PureFn, PureStmt};
use super::marker::{DebugMarker, MARKER_PREFIX};
use crate::Mutation;
#[derive(Debug, Clone)]
pub enum RemovalTarget {
All,
BySession(String),
OlderThan(u64),
ByDescription(String),
}
#[derive(Debug, Clone)]
pub struct RemoveDebugLogsMutation {
pub target: RemovalTarget,
}
impl RemoveDebugLogsMutation {
pub fn all() -> Self {
Self {
target: RemovalTarget::All,
}
}
pub fn by_session(session_id: impl Into<String>) -> Self {
Self {
target: RemovalTarget::BySession(session_id.into()),
}
}
pub fn older_than(timestamp: u64) -> Self {
Self {
target: RemovalTarget::OlderThan(timestamp),
}
}
pub fn by_description(pattern: impl Into<String>) -> Self {
Self {
target: RemovalTarget::ByDescription(pattern.into()),
}
}
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),
}
}
fn has_removable_marker(&self, expr: &PureExpr) -> bool {
match expr {
PureExpr::Macro { name, tokens, .. } => {
if name == "dbg" && DebugMarker::contains_marker(tokens) {
if let Some(marker) = self.extract_marker_from_tokens(tokens) {
return self.should_remove(&marker);
}
}
false
}
_ => false,
}
}
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,
}
}
fn extract_marker_from_tokens(&self, tokens: &str) -> Option<DebugMarker> {
let string_prefix = format!("\"{}:", MARKER_PREFIX);
if let Some(start) = tokens.find(&string_prefix) {
let rest = &tokens[start + 1..]; if let Some(end) = rest.find('"') {
let marker_content = &rest[..end];
return DebugMarker::from_comment(marker_content);
}
}
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
}
fn extract_dbg_inner(&self, tokens: &str) -> Option<String> {
let string_prefix = format!("\"{}:", MARKER_PREFIX);
if let Some(start) = tokens.find(&string_prefix) {
let rest = &tokens[start + 1..]; if let Some(quote_end) = rest.find('"') {
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());
}
}
}
}
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
}
fn transform_expr(&self, expr: &PureExpr) -> (PureExpr, usize) {
match expr {
PureExpr::Macro { name, tokens, .. } if name == "dbg" => {
if self.has_removable_marker(expr) {
if let Some(inner) = self.extract_dbg_inner(tokens) {
return (PureExpr::Other(inner), 1);
}
}
(expr.clone(), 0)
}
PureExpr::MethodCall {
receiver,
method,
args,
..
} => {
let (new_receiver, mut count) = self.transform_expr(receiver);
if method == "inspect" && args.len() == 1 {
if let PureExpr::Closure { body, .. } = &args[0] {
if self.block_has_removable_marker(body) {
return (new_receiver, count + 1);
}
}
}
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,
)
}
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),
}
}
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)
}
fn transform_stmt(&self, stmt: &PureStmt) -> (PureStmt, usize) {
match stmt {
PureStmt::Local { pattern, ty, init } => {
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),
},
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),
}
}
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())
}
}