Skip to main content

ryo_executor/executor/registry/converters/
module.rs

1//! ModuleConverter: Converts MutationSpec::RemoveMod, CreateMod
2//!
3//! Note: AddMod was consolidated into CreateMod.
4
5use 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/// Converter for Module mutations (RemoveMod, CreateMod)
14#[derive(Debug, Clone, Default)]
15pub struct ModuleConverter;
16
17impl ModuleConverter {
18    pub fn new() -> Self {
19        Self
20    }
21}
22
23// ModuleConverter uses the default implementation of ResolveTargetSymbol
24impl 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                // Resolve target_symbol to SymbolId (parent module)
42                let parent_id = self.resolve_target_symbol(target_symbol, ctx)?;
43
44                // Find the child module within the parent
45                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                // Verify it's a module
61                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                // Resolve target_symbol to SymbolId (parent module)
78                let symbol_id = self.resolve_target_symbol(target_symbol, ctx)?;
79
80                // Verify the target is a module
81                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}