ryo-mutations 0.1.0

[experimental] Code transformation primitives for Rust source code
Documentation
//! AddItemMutation, RemoveItemMutation, and MoveItemMutation
//!
//! Add, remove, or move arbitrary items from source code.

use crate::Mutation;
use ryo_source::pure::PureItem;
use ryo_source::ItemKind;
use ryo_symbol::{SymbolId, SymbolPath};

/// Add an item from raw source code content
#[derive(Debug, Clone)]
pub struct AddItemMutation {
    /// Parent module SymbolId where the item should be added
    pub parent: SymbolId,
    /// Raw source code content to parse and add
    pub content: String,
}

impl AddItemMutation {
    pub fn new(parent: SymbolId, content: impl Into<String>) -> Self {
        Self {
            parent,
            content: content.into(),
        }
    }
}

impl Mutation for AddItemMutation {
    fn mutation_type(&self) -> &'static str {
        "AddItem"
    }

    fn describe(&self) -> String {
        format!("Add item to {}", self.parent)
    }

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

/// Add multiple items directly from AST (no parsing overhead)
///
/// Unlike AddItemMutation which parses string content, this mutation
/// takes pre-built PureItem AST nodes. Used by Duplicate operations
/// to avoid AST → String → AST round-trips.
#[derive(Debug, Clone)]
pub struct AddPureItemsMutation {
    /// Parent module SymbolId where items should be added
    pub parent: SymbolId,
    /// Pre-built AST items to add
    pub items: Vec<PureItem>,
}

impl AddPureItemsMutation {
    pub fn new(parent: SymbolId, items: Vec<PureItem>) -> Self {
        Self { parent, items }
    }
}

impl Mutation for AddPureItemsMutation {
    fn mutation_type(&self) -> &'static str {
        "AddPureItems"
    }

    fn describe(&self) -> String {
        format!("Add {} items to {}", self.items.len(), self.parent)
    }

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

/// Remove an item by SymbolId
#[derive(Debug, Clone)]
pub struct RemoveItemMutation {
    /// SymbolId of the item to remove (required, O(1) lookup)
    pub symbol_id: SymbolId,
    /// Kind of item to remove
    pub item_kind: ItemKind,
}

impl RemoveItemMutation {
    pub fn new(symbol_id: SymbolId, item_kind: ItemKind) -> Self {
        Self {
            symbol_id,
            item_kind,
        }
    }
}

impl Mutation for RemoveItemMutation {
    fn mutation_type(&self) -> &'static str {
        "RemoveItem"
    }

    fn describe(&self) -> String {
        format!("Remove {:?} ({})", self.item_kind, self.symbol_id)
    }

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

/// Move an item from one module to another
#[derive(Debug, Clone)]
pub struct MoveItemMutation {
    /// Source module as SymbolPath
    pub source: SymbolPath,
    /// Target module as SymbolPath
    pub target: SymbolPath,
    /// Item name to move
    pub item_name: String,
    /// Kind of item to move
    pub item_kind: ItemKind,
    /// Whether to add use statement in source file
    pub add_use: bool,
}

impl MoveItemMutation {
    pub fn new(
        source: SymbolPath,
        target: SymbolPath,
        item_name: impl Into<String>,
        item_kind: ItemKind,
        add_use: bool,
    ) -> Self {
        Self {
            source,
            target,
            item_name: item_name.into(),
            item_kind,
            add_use,
        }
    }
}

impl Mutation for MoveItemMutation {
    fn mutation_type(&self) -> &'static str {
        "MoveItem"
    }

    fn describe(&self) -> String {
        format!(
            "Move {:?} '{}' from {} to {}",
            self.item_kind, self.item_name, self.source, self.target
        )
    }

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

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_add_item_mutation_describe() {
        let mutation = AddItemMutation::new(SymbolId::default(), "pub struct Foo {}");
        assert!(mutation.describe().contains("Add item"));
    }

    #[test]
    fn test_remove_item_mutation_describe() {
        let mutation = RemoveItemMutation::new(SymbolId::default(), ItemKind::Struct);
        assert!(mutation.describe().contains("Struct"));
    }
}