method-ray 0.1.2 → 0.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,39 +1,180 @@
1
- //! Definition Handlers - Processing Ruby class/method definitions
1
+ //! Definition Handlers - Processing Ruby class/method/module definitions
2
2
  //!
3
3
  //! This module is responsible for:
4
4
  //! - Class definition scope management (class Foo ... end)
5
- //! - Method definition scope management (def bar ... end)
6
- //! - Extracting class names from AST nodes
5
+ //! - Module definition scope management (module Bar ... end)
6
+ //! - Method definition scope management (def baz ... end)
7
+ //! - Extracting class/module names from AST nodes (including qualified names like Api::User)
7
8
 
8
- use crate::env::GlobalEnv;
9
+ use crate::env::{GlobalEnv, LocalEnv};
10
+ use crate::graph::{ChangeSet, VertexId};
11
+ use crate::types::Type;
12
+ use ruby_prism::Node;
13
+
14
+ use super::install::install_statements;
15
+ use super::parameters::install_parameters;
16
+
17
+ /// Process class definition node
18
+ pub(crate) fn process_class_node(
19
+ genv: &mut GlobalEnv,
20
+ lenv: &mut LocalEnv,
21
+ changes: &mut ChangeSet,
22
+ source: &str,
23
+ class_node: &ruby_prism::ClassNode,
24
+ ) -> Option<VertexId> {
25
+ let class_name = extract_class_name(class_node);
26
+ install_class(genv, class_name);
27
+
28
+ if let Some(body) = class_node.body() {
29
+ if let Some(statements) = body.as_statements_node() {
30
+ install_statements(genv, lenv, changes, source, &statements);
31
+ }
32
+ }
33
+
34
+ exit_scope(genv);
35
+ None
36
+ }
37
+
38
+ /// Process module definition node
39
+ pub(crate) fn process_module_node(
40
+ genv: &mut GlobalEnv,
41
+ lenv: &mut LocalEnv,
42
+ changes: &mut ChangeSet,
43
+ source: &str,
44
+ module_node: &ruby_prism::ModuleNode,
45
+ ) -> Option<VertexId> {
46
+ let module_name = extract_module_name(module_node);
47
+ install_module(genv, module_name);
48
+
49
+ if let Some(body) = module_node.body() {
50
+ if let Some(statements) = body.as_statements_node() {
51
+ install_statements(genv, lenv, changes, source, &statements);
52
+ }
53
+ }
54
+
55
+ exit_scope(genv);
56
+ None
57
+ }
58
+
59
+ /// Process method definition node
60
+ pub(crate) fn process_def_node(
61
+ genv: &mut GlobalEnv,
62
+ lenv: &mut LocalEnv,
63
+ changes: &mut ChangeSet,
64
+ source: &str,
65
+ def_node: &ruby_prism::DefNode,
66
+ ) -> Option<VertexId> {
67
+ let method_name = String::from_utf8_lossy(def_node.name().as_slice()).to_string();
68
+ install_method(genv, method_name.clone());
69
+
70
+ // Process parameters BEFORE processing body
71
+ let param_vtxs = if let Some(params_node) = def_node.parameters() {
72
+ install_parameters(genv, lenv, changes, source, &params_node)
73
+ } else {
74
+ vec![]
75
+ };
76
+
77
+ let mut last_vtx = None;
78
+ if let Some(body) = def_node.body() {
79
+ if let Some(statements) = body.as_statements_node() {
80
+ last_vtx = install_statements(genv, lenv, changes, source, &statements);
81
+ }
82
+ }
83
+
84
+ // Register user-defined method with return vertex and param vertices (before exiting scope)
85
+ if let Some(return_vtx) = last_vtx {
86
+ let recv_type_name = genv
87
+ .scope_manager
88
+ .current_class_name()
89
+ .or_else(|| genv.scope_manager.current_module_name());
90
+
91
+ if let Some(name) = recv_type_name {
92
+ genv.register_user_method(
93
+ Type::instance(&name),
94
+ &method_name,
95
+ return_vtx,
96
+ param_vtxs,
97
+ );
98
+ }
99
+ }
100
+
101
+ exit_scope(genv);
102
+ None
103
+ }
9
104
 
10
105
  /// Install class definition
11
- pub fn install_class(genv: &mut GlobalEnv, class_name: String) {
106
+ fn install_class(genv: &mut GlobalEnv, class_name: String) {
12
107
  genv.enter_class(class_name);
13
108
  }
14
109
 
110
+ /// Install module definition
111
+ fn install_module(genv: &mut GlobalEnv, module_name: String) {
112
+ genv.enter_module(module_name);
113
+ }
114
+
15
115
  /// Install method definition
16
- pub fn install_method(genv: &mut GlobalEnv, method_name: String) {
116
+ fn install_method(genv: &mut GlobalEnv, method_name: String) {
17
117
  genv.enter_method(method_name);
18
118
  }
19
119
 
20
- /// Exit current scope (class or method)
21
- pub fn exit_scope(genv: &mut GlobalEnv) {
120
+ /// Exit current scope (class, module, or method)
121
+ fn exit_scope(genv: &mut GlobalEnv) {
22
122
  genv.exit_scope();
23
123
  }
24
124
 
25
125
  /// Extract class name from ClassNode
26
- pub fn extract_class_name(class_node: &ruby_prism::ClassNode) -> String {
27
- if let Some(constant_read) = class_node.constant_path().as_constant_read_node() {
28
- String::from_utf8_lossy(constant_read.name().as_slice()).to_string()
29
- } else {
30
- "UnknownClass".to_string()
126
+ /// Supports both simple names (User) and qualified names (Api::V1::User)
127
+ fn extract_class_name(class_node: &ruby_prism::ClassNode) -> String {
128
+ extract_constant_path(&class_node.constant_path()).unwrap_or_else(|| "UnknownClass".to_string())
129
+ }
130
+
131
+ /// Extract module name from ModuleNode
132
+ /// Supports both simple names (Utils) and qualified names (Api::V1::Utils)
133
+ fn extract_module_name(module_node: &ruby_prism::ModuleNode) -> String {
134
+ extract_constant_path(&module_node.constant_path())
135
+ .unwrap_or_else(|| "UnknownModule".to_string())
136
+ }
137
+
138
+ /// Extract constant path from a Node (handles both ConstantReadNode and ConstantPathNode)
139
+ ///
140
+ /// Examples:
141
+ /// - `User` (ConstantReadNode) → "User"
142
+ /// - `Api::User` (ConstantPathNode) → "Api::User"
143
+ /// - `Api::V1::User` (nested ConstantPathNode) → "Api::V1::User"
144
+ /// - `::Api::User` (absolute path with COLON3) → "Api::User"
145
+ pub(crate) fn extract_constant_path(node: &Node) -> Option<String> {
146
+ // Simple constant read: `User`
147
+ if let Some(constant_read) = node.as_constant_read_node() {
148
+ return Some(String::from_utf8_lossy(constant_read.name().as_slice()).to_string());
149
+ }
150
+
151
+ // Constant path: `Api::User` or `Api::V1::User`
152
+ if let Some(constant_path) = node.as_constant_path_node() {
153
+ // name() returns Option<ConstantId>, use as_slice() to get &[u8]
154
+ let name = constant_path
155
+ .name()
156
+ .map(|id| String::from_utf8_lossy(id.as_slice()).to_string())?;
157
+
158
+ // Get parent path if exists
159
+ if let Some(parent_node) = constant_path.parent() {
160
+ if let Some(parent_path) = extract_constant_path(&parent_node) {
161
+ return Some(format!("{}::{}", parent_path, name));
162
+ }
163
+ }
164
+
165
+ // No parent (absolute path like `::User`)
166
+ return Some(name);
31
167
  }
168
+
169
+ None
32
170
  }
33
171
 
34
172
  #[cfg(test)]
35
173
  mod tests {
36
174
  use super::*;
175
+ use crate::graph::ChangeSet;
176
+ use crate::parser::ParseSession;
177
+ use crate::types::Type;
37
178
 
38
179
  #[test]
39
180
  fn test_enter_exit_class_scope() {
@@ -49,6 +190,20 @@ mod tests {
49
190
  assert_eq!(genv.scope_manager.current_class_name(), None);
50
191
  }
51
192
 
193
+ #[test]
194
+ fn test_enter_exit_module_scope() {
195
+ let mut genv = GlobalEnv::new();
196
+
197
+ install_module(&mut genv, "Utils".to_string());
198
+ assert_eq!(
199
+ genv.scope_manager.current_module_name(),
200
+ Some("Utils".to_string())
201
+ );
202
+
203
+ exit_scope(&mut genv);
204
+ assert_eq!(genv.scope_manager.current_module_name(), None);
205
+ }
206
+
52
207
  #[test]
53
208
  fn test_nested_method_scope() {
54
209
  let mut genv = GlobalEnv::new();
@@ -67,4 +222,116 @@ mod tests {
67
222
 
68
223
  assert_eq!(genv.scope_manager.current_class_name(), None);
69
224
  }
225
+
226
+ #[test]
227
+ fn test_method_in_module() {
228
+ let mut genv = GlobalEnv::new();
229
+
230
+ install_module(&mut genv, "Helpers".to_string());
231
+ install_method(&mut genv, "format".to_string());
232
+
233
+ // Should find module context from within method
234
+ assert_eq!(
235
+ genv.scope_manager.current_module_name(),
236
+ Some("Helpers".to_string())
237
+ );
238
+
239
+ exit_scope(&mut genv); // exit method
240
+ exit_scope(&mut genv); // exit module
241
+
242
+ assert_eq!(genv.scope_manager.current_module_name(), None);
243
+ }
244
+
245
+ #[test]
246
+ fn test_extract_simple_class_name() {
247
+ let source = "class User; end";
248
+ let session = ParseSession::new();
249
+ let parse_result = session.parse_source(source, "test.rb").unwrap();
250
+ let root = parse_result.node();
251
+ let program = root.as_program_node().unwrap();
252
+ let stmt = program.statements().body().first().unwrap();
253
+ let class_node = stmt.as_class_node().unwrap();
254
+
255
+ let name = extract_class_name(&class_node);
256
+ assert_eq!(name, "User");
257
+ }
258
+
259
+ #[test]
260
+ fn test_extract_qualified_class_name() {
261
+ let source = "class Api::User; end";
262
+ let session = ParseSession::new();
263
+ let parse_result = session.parse_source(source, "test.rb").unwrap();
264
+ let root = parse_result.node();
265
+ let program = root.as_program_node().unwrap();
266
+ let stmt = program.statements().body().first().unwrap();
267
+ let class_node = stmt.as_class_node().unwrap();
268
+
269
+ let name = extract_class_name(&class_node);
270
+ assert_eq!(name, "Api::User");
271
+ }
272
+
273
+ #[test]
274
+ fn test_extract_deeply_qualified_class_name() {
275
+ let source = "class Api::V1::Admin::User; end";
276
+ let session = ParseSession::new();
277
+ let parse_result = session.parse_source(source, "test.rb").unwrap();
278
+ let root = parse_result.node();
279
+ let program = root.as_program_node().unwrap();
280
+ let stmt = program.statements().body().first().unwrap();
281
+ let class_node = stmt.as_class_node().unwrap();
282
+
283
+ let name = extract_class_name(&class_node);
284
+ assert_eq!(name, "Api::V1::Admin::User");
285
+ }
286
+
287
+ #[test]
288
+ fn test_extract_simple_module_name() {
289
+ let source = "module Utils; end";
290
+ let session = ParseSession::new();
291
+ let parse_result = session.parse_source(source, "test.rb").unwrap();
292
+ let root = parse_result.node();
293
+ let program = root.as_program_node().unwrap();
294
+ let stmt = program.statements().body().first().unwrap();
295
+ let module_node = stmt.as_module_node().unwrap();
296
+
297
+ let name = extract_module_name(&module_node);
298
+ assert_eq!(name, "Utils");
299
+ }
300
+
301
+ #[test]
302
+ fn test_extract_qualified_module_name() {
303
+ let source = "module Api::V1; end";
304
+ let session = ParseSession::new();
305
+ let parse_result = session.parse_source(source, "test.rb").unwrap();
306
+ let root = parse_result.node();
307
+ let program = root.as_program_node().unwrap();
308
+ let stmt = program.statements().body().first().unwrap();
309
+ let module_node = stmt.as_module_node().unwrap();
310
+
311
+ let name = extract_module_name(&module_node);
312
+ assert_eq!(name, "Api::V1");
313
+ }
314
+
315
+ #[test]
316
+ fn test_process_def_node_registers_user_method() {
317
+ let source = "class User; def name; \"Alice\"; end; end";
318
+ let session = ParseSession::new();
319
+ let parse_result = session.parse_source(source, "test.rb").unwrap();
320
+ let root = parse_result.node();
321
+ let program = root.as_program_node().unwrap();
322
+
323
+ let mut genv = GlobalEnv::new();
324
+ let mut lenv = LocalEnv::new();
325
+ let mut changes = ChangeSet::new();
326
+
327
+ let stmt = program.statements().body().first().unwrap();
328
+ let class_node = stmt.as_class_node().unwrap();
329
+ process_class_node(&mut genv, &mut lenv, &mut changes, source, &class_node);
330
+
331
+ // User#name should be registered as a user-defined method
332
+ let info = genv
333
+ .resolve_method(&Type::instance("User"), "name")
334
+ .expect("User#name should be registered");
335
+ assert!(info.return_vertex.is_some());
336
+ }
70
337
  }