ryo-executor 0.1.0

[experimental] Mutation execution engine for RYO - parallel execution, conflict detection, workspace management
Documentation
//! ModuleConverter: Converts MutationSpec::RemoveMod, CreateMod
//!
//! Note: AddMod was consolidated into CreateMod.

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::{CreateModMutation, RemoveModMutation};
use ryo_symbol::SymbolKind;

/// Converter for Module mutations (RemoveMod, CreateMod)
#[derive(Debug, Clone, Default)]
pub struct ModuleConverter;

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

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

impl MutationConverter for ModuleConverter {
    fn spec_kinds(&self) -> &'static [&'static str] {
        &["RemoveMod", "CreateMod"]
    }

    fn convert_v2(
        &self,
        spec: &MutationSpec,
        ctx: &AnalysisContext,
    ) -> Result<Vec<Box<dyn ASTRegApply>>, ConvertError> {
        match spec {
            MutationSpec::RemoveMod {
                target: target_symbol,
                mod_name,
            } => {
                // Resolve target_symbol to SymbolId (parent module)
                let parent_id = self.resolve_target_symbol(target_symbol, ctx)?;

                // Find the child module within the parent
                let parent_path = ctx.registry.resolve(parent_id).ok_or_else(|| {
                    ConvertError::TargetNotFound(format!("Parent module {} not found", parent_id))
                })?;

                let mod_path = parent_path.child(mod_name).map_err(|e| {
                    ConvertError::TargetNotFound(format!("Failed to build path: {}", e))
                })?;

                let module_id = ctx.registry.lookup(&mod_path).ok_or_else(|| {
                    ConvertError::TargetNotFound(format!(
                        "Module '{}' not found in {}",
                        mod_name, parent_path
                    ))
                })?;

                // Verify it's a module
                if ctx.registry.kind(module_id) != Some(SymbolKind::Mod) {
                    return Err(ConvertError::TargetNotFound(format!(
                        "Symbol {} is not a module",
                        module_id
                    )));
                }

                let mutation = RemoveModMutation::new(module_id);
                Ok(vec![Box::new(mutation)])
            }
            MutationSpec::CreateMod {
                target: target_symbol,
                mod_name,
                content,
                is_pub,
            } => {
                // Resolve target_symbol to SymbolId (parent module)
                let symbol_id = self.resolve_target_symbol(target_symbol, ctx)?;

                // Verify the target is a module
                if ctx.registry.kind(symbol_id) != Some(SymbolKind::Mod) {
                    return Err(ConvertError::TargetNotFound(format!(
                        "Target symbol {:?} is not a module",
                        symbol_id
                    )));
                }

                let mut mutation = CreateModMutation::new(symbol_id, mod_name.clone());
                if !content.is_empty() {
                    mutation = mutation.with_content(content.clone());
                }
                if *is_pub {
                    mutation = mutation.public();
                }
                Ok(vec![Box::new(mutation)])
            }
            _ => Err(ConvertError::TypeMismatch {
                expected: "RemoveMod or CreateMod",
                actual: spec.kind_name().to_string(),
            }),
        }
    }
}

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

    #[test]
    fn test_module_converter_spec_kinds() {
        let converter = ModuleConverter::new();
        assert_eq!(converter.spec_kinds(), &["RemoveMod", "CreateMod"]);
    }

    #[test]
    fn test_module_converter_can_handle_remove() {
        let converter = ModuleConverter::new();

        let spec = MutationSpec::RemoveMod {
            target: crate::executor::spec::MutationTargetSymbol::ByPath(Box::new(
                SymbolPath::parse("test_crate").unwrap(),
            )),
            mod_name: "models".into(),
        };
        assert!(converter.can_handle(&spec));
    }

    #[test]
    fn test_module_converter_can_handle_create() {
        let converter = ModuleConverter::new();

        let spec = MutationSpec::CreateMod {
            target: crate::executor::spec::MutationTargetSymbol::ByPath(Box::new(
                SymbolPath::parse("test_crate").unwrap(),
            )),
            mod_name: "utils".into(),
            content: String::new(),
            is_pub: false,
        };
        assert!(converter.can_handle(&spec));
    }
}