ryo_executor/executor/registry/converters/
module.rs1use crate::engine::ASTRegApply;
6use crate::executor::registry::converter::{ConvertError, MutationConverter};
7use crate::executor::registry::converters::ResolveTargetSymbol;
8use crate::executor::spec::MutationSpec;
9use ryo_analysis::AnalysisContext;
10use ryo_mutations::{CreateModMutation, RemoveModMutation};
11use ryo_symbol::SymbolKind;
12
13#[derive(Debug, Clone, Default)]
15pub struct ModuleConverter;
16
17impl ModuleConverter {
18 pub fn new() -> Self {
19 Self
20 }
21}
22
23impl ResolveTargetSymbol for ModuleConverter {}
25
26impl MutationConverter for ModuleConverter {
27 fn spec_kinds(&self) -> &'static [&'static str] {
28 &["RemoveMod", "CreateMod"]
29 }
30
31 fn convert_v2(
32 &self,
33 spec: &MutationSpec,
34 ctx: &AnalysisContext,
35 ) -> Result<Vec<Box<dyn ASTRegApply>>, ConvertError> {
36 match spec {
37 MutationSpec::RemoveMod {
38 target: target_symbol,
39 mod_name,
40 } => {
41 let parent_id = self.resolve_target_symbol(target_symbol, ctx)?;
43
44 let parent_path = ctx.registry.resolve(parent_id).ok_or_else(|| {
46 ConvertError::TargetNotFound(format!("Parent module {} not found", parent_id))
47 })?;
48
49 let mod_path = parent_path.child(mod_name).map_err(|e| {
50 ConvertError::TargetNotFound(format!("Failed to build path: {}", e))
51 })?;
52
53 let module_id = ctx.registry.lookup(&mod_path).ok_or_else(|| {
54 ConvertError::TargetNotFound(format!(
55 "Module '{}' not found in {}",
56 mod_name, parent_path
57 ))
58 })?;
59
60 if ctx.registry.kind(module_id) != Some(SymbolKind::Mod) {
62 return Err(ConvertError::TargetNotFound(format!(
63 "Symbol {} is not a module",
64 module_id
65 )));
66 }
67
68 let mutation = RemoveModMutation::new(module_id);
69 Ok(vec![Box::new(mutation)])
70 }
71 MutationSpec::CreateMod {
72 target: target_symbol,
73 mod_name,
74 content,
75 is_pub,
76 } => {
77 let symbol_id = self.resolve_target_symbol(target_symbol, ctx)?;
79
80 if ctx.registry.kind(symbol_id) != Some(SymbolKind::Mod) {
82 return Err(ConvertError::TargetNotFound(format!(
83 "Target symbol {:?} is not a module",
84 symbol_id
85 )));
86 }
87
88 let mut mutation = CreateModMutation::new(symbol_id, mod_name.clone());
89 if !content.is_empty() {
90 mutation = mutation.with_content(content.clone());
91 }
92 if *is_pub {
93 mutation = mutation.public();
94 }
95 Ok(vec![Box::new(mutation)])
96 }
97 _ => Err(ConvertError::TypeMismatch {
98 expected: "RemoveMod or CreateMod",
99 actual: spec.kind_name().to_string(),
100 }),
101 }
102 }
103}
104
105#[cfg(test)]
106mod tests {
107 use super::*;
108 use ryo_analysis::SymbolPath;
109
110 #[test]
111 fn test_module_converter_spec_kinds() {
112 let converter = ModuleConverter::new();
113 assert_eq!(converter.spec_kinds(), &["RemoveMod", "CreateMod"]);
114 }
115
116 #[test]
117 fn test_module_converter_can_handle_remove() {
118 let converter = ModuleConverter::new();
119
120 let spec = MutationSpec::RemoveMod {
121 target: crate::executor::spec::MutationTargetSymbol::ByPath(Box::new(
122 SymbolPath::parse("test_crate").unwrap(),
123 )),
124 mod_name: "models".into(),
125 };
126 assert!(converter.can_handle(&spec));
127 }
128
129 #[test]
130 fn test_module_converter_can_handle_create() {
131 let converter = ModuleConverter::new();
132
133 let spec = MutationSpec::CreateMod {
134 target: crate::executor::spec::MutationTargetSymbol::ByPath(Box::new(
135 SymbolPath::parse("test_crate").unwrap(),
136 )),
137 mod_name: "utils".into(),
138 content: String::new(),
139 is_pub: false,
140 };
141 assert!(converter.can_handle(&spec));
142 }
143}