roblox_rs_core/ast/
visitor.rs1use syn::{visit::{self, Visit}, File, ItemFn, ItemStruct, ItemEnum, ItemImpl, ImplItem};
6
7pub struct AstCollector {
9 pub functions: Vec<String>,
11 pub structs: Vec<String>,
13 pub enums: Vec<String>,
15 pub imports: Vec<String>,
17}
18
19impl AstCollector {
20 pub fn new() -> Self {
22 Self {
23 functions: Vec::new(),
24 structs: Vec::new(),
25 enums: Vec::new(),
26 imports: Vec::new(),
27 }
28 }
29
30 pub fn analyze(&mut self, file: &File) {
32 self.visit_file(file);
33 }
34}
35
36impl<'ast> Visit<'ast> for AstCollector {
37 fn visit_item_fn(&mut self, node: &'ast ItemFn) {
38 self.functions.push(node.sig.ident.to_string());
40
41 visit::visit_item_fn(self, node);
43 }
44
45 fn visit_item_struct(&mut self, node: &'ast ItemStruct) {
46 self.structs.push(node.ident.to_string());
48
49 visit::visit_item_struct(self, node);
51 }
52
53 fn visit_item_enum(&mut self, node: &'ast ItemEnum) {
54 self.enums.push(node.ident.to_string());
56
57 visit::visit_item_enum(self, node);
59 }
60
61 fn visit_item_use(&mut self, node: &'ast syn::ItemUse) {
62 let path = format!("{:?}", node.tree);
64 self.imports.push(path);
65
66 visit::visit_item_use(self, node);
68 }
69}
70
71pub struct DependencyAnalyzer {
73 pub external_crates: Vec<String>,
75 pub std_modules: Vec<String>,
77}
78
79impl DependencyAnalyzer {
80 pub fn new() -> Self {
82 Self {
83 external_crates: Vec::new(),
84 std_modules: Vec::new(),
85 }
86 }
87
88 pub fn analyze(&mut self, file: &File) {
90 self.visit_file(file);
91 }
92}
93
94impl<'ast> Visit<'ast> for DependencyAnalyzer {
95 fn visit_use_path(&mut self, path: &'ast syn::UsePath) {
96 let name = path.ident.to_string();
98
99 if name == "std" {
100 if let syn::UseTree::Path(subtree) = &*path.tree {
102 let module = subtree.ident.to_string();
103 if !self.std_modules.contains(&module) {
104 self.std_modules.push(module);
105 }
106 }
107 } else if !name.starts_with("self") && !name.starts_with("crate") {
108 if !self.external_crates.contains(&name) {
110 self.external_crates.push(name);
111 }
112 }
113
114 visit::visit_use_path(self, path);
116 }
117}
118
119pub struct CompatibilityChecker {
121 pub issues: Vec<String>,
123}
124
125impl CompatibilityChecker {
126 pub fn new() -> Self {
128 Self {
129 issues: Vec::new(),
130 }
131 }
132
133 pub fn check(&mut self, file: &File) {
135 self.visit_file(file);
136 }
137}
138
139impl<'ast> Visit<'ast> for CompatibilityChecker {
140 fn visit_item_fn(&mut self, node: &'ast ItemFn) {
141 if node.sig.asyncness.is_some() {
143 self.issues.push(format!(
144 "Async function '{}' may need special handling for Luau",
145 node.sig.ident
146 ));
147 }
148
149 if node.sig.unsafety.is_some() {
151 self.issues.push(format!(
152 "Unsafe function '{}' will need manual safety checks in Luau",
153 node.sig.ident
154 ));
155 }
156
157 visit::visit_item_fn(self, node);
159 }
160
161 fn visit_expr_method_call(&mut self, node: &'ast syn::ExprMethodCall) {
162 let method_name = node.method.to_string();
164
165 if ["thread_local", "spawn", "catch_unwind"].contains(&method_name.as_str()) {
167 self.issues.push(format!(
168 "Method call '{}' might not be directly translatable to Luau",
169 method_name
170 ));
171 }
172
173 visit::visit_expr_method_call(self, node);
175 }
176}
177
178#[cfg(test)]
179mod tests {
180 use super::*;
181 use syn::parse_quote;
182
183 #[test]
184 fn test_ast_collector() {
185 let code = parse_quote! {
186 fn hello() {
187 println!("Hello");
188 }
189
190 struct Point {
191 x: f32,
192 y: f32,
193 }
194
195 enum Direction {
196 North,
197 South,
198 East,
199 West,
200 }
201 };
202
203 let mut collector = AstCollector::new();
204 collector.analyze(&code);
205
206 assert_eq!(collector.functions.len(), 1);
207 assert_eq!(collector.functions[0], "hello");
208
209 assert_eq!(collector.structs.len(), 1);
210 assert_eq!(collector.structs[0], "Point");
211
212 assert_eq!(collector.enums.len(), 1);
213 assert_eq!(collector.enums[0], "Direction");
214 }
215}