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;
#[derive(Debug, Clone, Default)]
pub struct ModuleConverter;
impl ModuleConverter {
pub fn new() -> Self {
Self
}
}
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,
} => {
let parent_id = self.resolve_target_symbol(target_symbol, ctx)?;
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
))
})?;
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,
} => {
let symbol_id = self.resolve_target_symbol(target_symbol, ctx)?;
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));
}
}