ryo-executor 0.1.0

[experimental] Mutation execution engine for RYO - parallel execution, conflict detection, workspace management
Documentation
//! MoveConverter: Converts MoveItem MutationSpec to MoveItemMutation
//!
//! Handles cross-file move operations via ASTRegistry:
//! - Remove item from source module
//! - Register item at target module
//! - Optionally add use statement in source
//!
//! ## V2 Implementation
//!
//! MoveItem operates on SymbolPath (not file paths), making it fully compatible
//! with the V2 ASTRegistry-based approach. FileDumper determines output files
//! from the SymbolPath after the move operation.

use crate::engine::ASTRegApply;
use crate::executor::registry::converter::{ConvertError, MutationConverter};
use crate::executor::registry::converters::ResolveTargetSymbol;
use crate::executor::spec::MutationSpec;
use ryo_analysis::AnalysisContext;
use ryo_mutations::MoveItemMutation;

/// Converter for MoveItem MutationSpec
pub struct MoveConverter;

impl MoveConverter {
    pub fn new() -> Self {
        Self
    }
}

impl Default for MoveConverter {
    fn default() -> Self {
        Self::new()
    }
}

// MoveConverter uses the default implementation of ResolveTargetSymbol
impl ResolveTargetSymbol for MoveConverter {}

impl MutationConverter for MoveConverter {
    fn spec_kinds(&self) -> &'static [&'static str] {
        &["MoveItem"]
    }

    fn convert_v2(
        &self,
        spec: &MutationSpec,
        ctx: &AnalysisContext,
    ) -> Result<Vec<Box<dyn ASTRegApply>>, ConvertError> {
        match spec {
            MutationSpec::MoveItem {
                source: source_symbol,
                target: target_symbol,
                item_name,
                item_kind,
                add_use,
            } => {
                // Resolve source and target symbols to SymbolId, then to SymbolPath
                let source_id = self.resolve_target_symbol(source_symbol, ctx)?;
                let target_id = self.resolve_target_symbol(target_symbol, ctx)?;

                let source_path = ctx
                    .registry
                    .resolve(source_id)
                    .ok_or_else(|| {
                        ConvertError::TargetNotFound(format!(
                            "Source module {:?} not found",
                            source_id
                        ))
                    })?
                    .clone();

                let target_path = ctx
                    .registry
                    .resolve(target_id)
                    .ok_or_else(|| {
                        ConvertError::TargetNotFound(format!(
                            "Target module {:?} not found",
                            target_id
                        ))
                    })?
                    .clone();

                let mutation = MoveItemMutation::new(
                    source_path,
                    target_path,
                    item_name.clone(),
                    *item_kind,
                    *add_use,
                );
                Ok(vec![Box::new(mutation)])
            }
            _ => Err(ConvertError::TypeMismatch {
                expected: "MoveItem",
                actual: spec.kind_name().to_string(),
            }),
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::executor::spec::MutationSpec;
    use ryo_source::ItemKind;
    use ryo_symbol::{SymbolKind, SymbolPath, SymbolRegistry};

    #[test]
    fn test_move_converter_spec_kinds() {
        let converter = MoveConverter::new();
        assert_eq!(converter.spec_kinds(), &["MoveItem"]);
    }

    #[test]
    fn test_move_converter_can_handle() {
        let converter = MoveConverter::new();

        let move_spec = MutationSpec::MoveItem {
            source: crate::executor::spec::MutationTargetSymbol::ByPath(Box::new(
                SymbolPath::parse("test_crate").unwrap(),
            )),
            target: crate::executor::spec::MutationTargetSymbol::ByPath(Box::new(
                SymbolPath::parse("test_crate::core").unwrap(),
            )),
            item_name: "Task".into(),
            item_kind: ItemKind::Struct,
            add_use: true,
        };
        assert!(converter.can_handle(&move_spec));

        // Create a dummy symbol_id for testing
        let mut registry = SymbolRegistry::new();
        let path = SymbolPath::parse("test_crate::old").unwrap();
        let symbol_id = registry.register(path, SymbolKind::Function).unwrap();

        let other_spec = MutationSpec::Rename {
            target: crate::executor::spec::MutationTargetSymbol::ById(symbol_id),
            to: "new".into(),
            scope: crate::executor::spec::Scope::Project,
        };
        assert!(!converter.can_handle(&other_spec));
    }
}