Skip to main content

ryo_executor/executor/registry/converters/
move_.rs

1//! MoveConverter: Converts MoveItem MutationSpec to MoveItemMutation
2//!
3//! Handles cross-file move operations via ASTRegistry:
4//! - Remove item from source module
5//! - Register item at target module
6//! - Optionally add use statement in source
7//!
8//! ## V2 Implementation
9//!
10//! MoveItem operates on SymbolPath (not file paths), making it fully compatible
11//! with the V2 ASTRegistry-based approach. FileDumper determines output files
12//! from the SymbolPath after the move operation.
13
14use crate::engine::ASTRegApply;
15use crate::executor::registry::converter::{ConvertError, MutationConverter};
16use crate::executor::registry::converters::ResolveTargetSymbol;
17use crate::executor::spec::MutationSpec;
18use ryo_analysis::AnalysisContext;
19use ryo_mutations::MoveItemMutation;
20
21/// Converter for MoveItem MutationSpec
22pub struct MoveConverter;
23
24impl MoveConverter {
25    pub fn new() -> Self {
26        Self
27    }
28}
29
30impl Default for MoveConverter {
31    fn default() -> Self {
32        Self::new()
33    }
34}
35
36// MoveConverter uses the default implementation of ResolveTargetSymbol
37impl ResolveTargetSymbol for MoveConverter {}
38
39impl MutationConverter for MoveConverter {
40    fn spec_kinds(&self) -> &'static [&'static str] {
41        &["MoveItem"]
42    }
43
44    fn convert_v2(
45        &self,
46        spec: &MutationSpec,
47        ctx: &AnalysisContext,
48    ) -> Result<Vec<Box<dyn ASTRegApply>>, ConvertError> {
49        match spec {
50            MutationSpec::MoveItem {
51                source: source_symbol,
52                target: target_symbol,
53                item_name,
54                item_kind,
55                add_use,
56            } => {
57                // Resolve source and target symbols to SymbolId, then to SymbolPath
58                let source_id = self.resolve_target_symbol(source_symbol, ctx)?;
59                let target_id = self.resolve_target_symbol(target_symbol, ctx)?;
60
61                let source_path = ctx
62                    .registry
63                    .resolve(source_id)
64                    .ok_or_else(|| {
65                        ConvertError::TargetNotFound(format!(
66                            "Source module {:?} not found",
67                            source_id
68                        ))
69                    })?
70                    .clone();
71
72                let target_path = ctx
73                    .registry
74                    .resolve(target_id)
75                    .ok_or_else(|| {
76                        ConvertError::TargetNotFound(format!(
77                            "Target module {:?} not found",
78                            target_id
79                        ))
80                    })?
81                    .clone();
82
83                let mutation = MoveItemMutation::new(
84                    source_path,
85                    target_path,
86                    item_name.clone(),
87                    *item_kind,
88                    *add_use,
89                );
90                Ok(vec![Box::new(mutation)])
91            }
92            _ => Err(ConvertError::TypeMismatch {
93                expected: "MoveItem",
94                actual: spec.kind_name().to_string(),
95            }),
96        }
97    }
98}
99
100#[cfg(test)]
101mod tests {
102    use super::*;
103    use crate::executor::spec::MutationSpec;
104    use ryo_source::ItemKind;
105    use ryo_symbol::{SymbolKind, SymbolPath, SymbolRegistry};
106
107    #[test]
108    fn test_move_converter_spec_kinds() {
109        let converter = MoveConverter::new();
110        assert_eq!(converter.spec_kinds(), &["MoveItem"]);
111    }
112
113    #[test]
114    fn test_move_converter_can_handle() {
115        let converter = MoveConverter::new();
116
117        let move_spec = MutationSpec::MoveItem {
118            source: crate::executor::spec::MutationTargetSymbol::ByPath(Box::new(
119                SymbolPath::parse("test_crate").unwrap(),
120            )),
121            target: crate::executor::spec::MutationTargetSymbol::ByPath(Box::new(
122                SymbolPath::parse("test_crate::core").unwrap(),
123            )),
124            item_name: "Task".into(),
125            item_kind: ItemKind::Struct,
126            add_use: true,
127        };
128        assert!(converter.can_handle(&move_spec));
129
130        // Create a dummy symbol_id for testing
131        let mut registry = SymbolRegistry::new();
132        let path = SymbolPath::parse("test_crate::old").unwrap();
133        let symbol_id = registry.register(path, SymbolKind::Function).unwrap();
134
135        let other_spec = MutationSpec::Rename {
136            target: crate::executor::spec::MutationTargetSymbol::ById(symbol_id),
137            to: "new".into(),
138            scope: crate::executor::spec::Scope::Project,
139        };
140        assert!(!converter.can_handle(&other_spec));
141    }
142}