Skip to main content

ryo_executor/executor/
cascade_convert.rs

1//! CascadeSpec → MutationSpec 変換
2//!
3//! # 責務
4//!
5//! ryo-analysisで定義されたCascadeSpec(Policy)を
6//! 実行可能なMutationSpec(実装)に変換する。
7//!
8//! # 設計意図
9//!
10//! - ryo-analysisはPolicy(方針)を決定する
11//! - ryo-executorは実装(変換・実行)を担当する
12//! - この分離により、分析ロジックと実行ロジックが疎結合になる
13//!
14//! # Orphan Rule
15//!
16//! ryo-executorがryo-analysisに依存しており、MutationSpecはryo-executorで
17//! 定義されているため、`From<CascadeSpec> for MutationSpec`をここで実装可能。
18
19use ryo_analysis::cascade::{CascadeSpec, Visibility as CascadeVisibility};
20
21use super::spec::{InsertPosition, MutationSpec, MutationTargetSymbol, Visibility};
22
23impl From<CascadeSpec> for MutationSpec {
24    fn from(cascade: CascadeSpec) -> Self {
25        match cascade {
26            CascadeSpec::AddDerive { symbol_id, derives } => MutationSpec::AddDerive {
27                target: MutationTargetSymbol::ById(symbol_id),
28                derives,
29            },
30
31            CascadeSpec::GenerateImpl {
32                target,
33                trait_name,
34                call_new,
35            } => {
36                let body = if call_new {
37                    format!("Self {{ inner: {}::new() }}", target.name())
38                } else {
39                    "todo!()".to_string()
40                };
41
42                let parent = target.parent().unwrap_or_else(|| {
43                    ryo_analysis::SymbolPath::parse("test_crate")
44                        .expect("literal 'test_crate' is a valid SymbolPath")
45                });
46
47                MutationSpec::AddItem {
48                    target: MutationTargetSymbol::ByPath(Box::new(parent)),
49                    content: format!(
50                        "impl {} for {} {{\n    fn default() -> Self {{\n        {}\n    }}\n}}",
51                        trait_name,
52                        target.name(),
53                        body
54                    ),
55                    position: InsertPosition::Bottom,
56                }
57            }
58
59            CascadeSpec::ChangeVisibility {
60                symbol_id,
61                visibility,
62            } => MutationSpec::ChangeVisibility {
63                target: MutationTargetSymbol::ById(symbol_id),
64                visibility: convert_visibility(visibility),
65            },
66
67            CascadeSpec::AddUse {
68                target_module,
69                path,
70            } => MutationSpec::AddItem {
71                target: MutationTargetSymbol::ByPath(Box::new(target_module)),
72                content: format!("use {};", path),
73                position: InsertPosition::Top,
74            },
75
76            CascadeSpec::AddMatchArm {
77                target,
78                function_name,
79                enum_name,
80                pattern,
81                body,
82            } => {
83                // Construct function path from module path + function name
84                let fn_path = target
85                    .child(&function_name)
86                    .unwrap_or_else(|_| target.clone());
87                MutationSpec::AddMatchArm {
88                    target: MutationTargetSymbol::ByPath(Box::new(fn_path)),
89                    enum_name,
90                    pattern,
91                    body,
92                }
93            }
94
95            CascadeSpec::RemoveMatchArm {
96                target,
97                function_name,
98                enum_name,
99                pattern,
100            } => {
101                let fn_path = target
102                    .child(&function_name)
103                    .unwrap_or_else(|_| target.clone());
104                MutationSpec::RemoveMatchArm {
105                    target: MutationTargetSymbol::ByPath(Box::new(fn_path)),
106                    enum_name,
107                    pattern,
108                }
109            }
110        }
111    }
112}
113
114/// CascadeVisibility → Visibility 変換
115fn convert_visibility(vis: CascadeVisibility) -> Visibility {
116    match vis {
117        CascadeVisibility::Private => Visibility::Private,
118        CascadeVisibility::Crate => Visibility::PubCrate,
119        CascadeVisibility::Super => Visibility::PubSuper,
120        CascadeVisibility::Public => Visibility::Pub,
121    }
122}
123
124/// CascadeSpecのリストをMutationSpecのリストに変換
125pub fn convert_cascade_specs(specs: Vec<CascadeSpec>) -> Vec<MutationSpec> {
126    specs.into_iter().map(Into::into).collect()
127}
128
129#[cfg(test)]
130mod tests {
131    use super::*;
132    use ryo_analysis::SymbolPath;
133    use ryo_symbol::SymbolId;
134
135    /// Create a dummy SymbolId for testing
136    fn dummy_id(index: u32) -> SymbolId {
137        SymbolId::parse(&format!("{}v1", index)).expect("valid dummy id")
138    }
139
140    #[test]
141    fn test_add_derive_conversion() {
142        let _path = SymbolPath::parse("test_crate::Config").unwrap();
143        let symbol_id = dummy_id(1);
144        let cascade = CascadeSpec::AddDerive {
145            symbol_id,
146            derives: vec!["Default".to_string(), "Debug".to_string()],
147        };
148
149        let mutation: MutationSpec = cascade.into();
150
151        match mutation {
152            MutationSpec::AddDerive {
153                target, derives, ..
154            } => {
155                assert_eq!(target, MutationTargetSymbol::ById(symbol_id));
156                assert_eq!(derives, vec!["Default", "Debug"]);
157            }
158            _ => panic!("Expected AddDerive"),
159        }
160    }
161
162    #[test]
163    fn test_change_visibility_conversion() {
164        let _path = SymbolPath::parse("test_crate::internal::Helper").unwrap();
165        let symbol_id = dummy_id(2);
166        let cascade = CascadeSpec::ChangeVisibility {
167            symbol_id,
168            visibility: CascadeVisibility::Public,
169        };
170
171        let mutation: MutationSpec = cascade.into();
172
173        match mutation {
174            MutationSpec::ChangeVisibility {
175                target, visibility, ..
176            } => {
177                assert_eq!(target, MutationTargetSymbol::ById(symbol_id));
178                assert!(matches!(visibility, Visibility::Pub));
179            }
180            _ => panic!("Expected ChangeVisibility"),
181        }
182    }
183
184    #[test]
185    fn test_add_use_conversion() {
186        let path = SymbolPath::parse("test_crate::module").unwrap();
187        let cascade = CascadeSpec::AddUse {
188            target_module: path.clone(),
189            path: "std::collections::HashMap".to_string(),
190        };
191
192        let mutation: MutationSpec = cascade.into();
193
194        match mutation {
195            MutationSpec::AddItem {
196                target,
197                content,
198                position,
199            } => {
200                if let MutationTargetSymbol::ByPath(target_path) = target {
201                    assert_eq!(*target_path, path);
202                } else {
203                    panic!("Expected ByPath target");
204                }
205                assert_eq!(content, "use std::collections::HashMap;");
206                assert_eq!(position, InsertPosition::Top);
207            }
208            _ => panic!("Expected AddItem"),
209        }
210    }
211}