Skip to content

Commit c9c2cc9

Browse files
committed
track ancestors
1 parent d732947 commit c9c2cc9

File tree

2 files changed

+86
-18
lines changed

2 files changed

+86
-18
lines changed

lib/helpers/parse_ruby.rb

Lines changed: 68 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -5,18 +5,50 @@
55

66
# Set number_of_workers to 1 to skip parallel.
77
with_configurations(number_of_workers: 1) do
8-
definitions = { modules: [], classes: [] }
8+
definitions = { modules: [], classes: [], constants: [] }
99
current_context = definitions
1010
context_stack = []
1111

12+
helper_method :find_in_definitions do |names, definitions|
13+
return nil if names.empty? || definitions.nil?
14+
15+
if names.length > 1
16+
# The first name is a module, find it and search within its definitions
17+
mod_name = names.shift
18+
definitions[:modules].each do |mod|
19+
if mod[:name] == mod_name
20+
return find_in_definitions(names, mod) # Recurse with the rest of the names
21+
end
22+
end
23+
else
24+
# The last name is a class, find it in the current definitions
25+
class_name = names.first
26+
definitions[:classes].each do |klass|
27+
return klass if klass[:name] == class_name
28+
end
29+
definitions[:modules].each do |mod|
30+
found = find_in_definitions([class_name], mod)
31+
return found if found
32+
end
33+
end
34+
35+
nil
36+
end
37+
38+
helper_method :find_class do |full_name, definitions|
39+
names = full_name.split('::')
40+
find_in_definitions(names, definitions)
41+
end
42+
1243
within_file Synvert::ALL_RUBY_FILES do
1344
add_callback :module_node, at: 'start' do |node|
1445
name = node.constant_path.to_source
1546
existing_module = current_context[:modules].find { |mod| mod[:name] == name }
1647
if existing_module
1748
new_context = existing_module
1849
else
19-
new_context = { name: name, modules: [], classes: [], methods: [], static_methods: [], constants: [] }
50+
full_name = [current_context[:full_name], name].compact.join('::')
51+
new_context = { name: name, full_name: full_name, modules: [], classes: [], methods: [], static_methods: [], singleton: [], constants: [] }
2052
current_context[:modules] << new_context
2153
end
2254

@@ -35,7 +67,8 @@
3567
if existing_class
3668
new_context = existing_class
3769
else
38-
new_context = { name: name, superclass: superclass, modules: [], classes: [], methods: [], static_methods: [], singleton: {}, constants: [], included_modules: [] }
70+
full_name = [current_context[:full_name], name].compact.join('::')
71+
new_context = { name: name, full_name: full_name, superclass: superclass, modules: [], classes: [], methods: [], static_methods: [], singleton: {}, constants: [], included_modules: [] }
3972
current_context[:classes] << new_context
4073
end
4174

@@ -52,7 +85,7 @@
5285
if !existing_singleton.empty?
5386
new_context = existing_singleton
5487
else
55-
new_context = { methods: [] }
88+
new_context = { methods: [], constants: [] }
5689
current_context[:singleton] = new_context
5790
end
5891

@@ -72,6 +105,11 @@
72105
if node.receiver.nil? && node.name == :include
73106
current_context[:included_modules] << node.arguments.arguments.first.to_source
74107
end
108+
109+
if node.name == :class_eval
110+
klass_definition = find_class(node.receiver.to_source, definitions)
111+
current_context = klass_definition if klass_definition
112+
end
75113
end
76114

77115
add_callback :def_node, at: 'start' do |node|
@@ -93,16 +131,32 @@
93131
end
94132
end
95133

96-
def find_class(name, definitions)
97-
definitions[:classes].each do |klass|
98-
return klass if klass[:name] == name
99-
found = find_class(name, klass)
100-
return found if found
101-
end
134+
def find_class(full_name, definitions)
135+
names = full_name.split('::')
136+
find_in_definitions(names, definitions)
137+
end
102138

103-
definitions[:modules].each do |mod|
104-
found = find_class(name, mod)
105-
return found if found
139+
def find_in_definitions(names, definitions)
140+
return nil if names.empty? || definitions.nil?
141+
142+
if names.length > 1
143+
# The first name is a module, find it and search within its definitions
144+
mod_name = names.shift
145+
definitions[:modules].each do |mod|
146+
if mod[:name] == mod_name
147+
return find_in_definitions(names, mod) # Recurse with the rest of the names
148+
end
149+
end
150+
else
151+
# The last name is a class, find it in the current definitions
152+
class_name = names.first
153+
definitions[:classes].each do |klass|
154+
return klass if klass[:name] == class_name
155+
end
156+
definitions[:modules].each do |mod|
157+
found = find_in_definitions([class_name], mod)
158+
return found if found
159+
end
106160
end
107161

108162
nil
@@ -113,8 +167,8 @@ def add_ancestors(definitions)
113167
ancestors = []
114168
superclass = klass[:superclass]
115169
while superclass
116-
ancestors << superclass
117170
superclass_class = find_class(superclass, definitions)
171+
ancestors << superclass_class[:full_name]
118172
if superclass_class
119173
ancestors.concat(superclass_class[:included_modules]) if superclass_class[:included_modules]
120174
superclass = superclass_class[:superclass]

spec/helpers/parse_ruby_spec.rb

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,6 @@
1414
File.write('app/models/user.rb', <<~EOF)
1515
module Synvert
1616
class User
17-
include Trackable
18-
1917
ROLES = %w[user admin].freeze
2018
2119
class << self
@@ -44,6 +42,14 @@ def user_type
4442
end
4543
end
4644
EOF
45+
FileUtils.mkdir_p('config/initializers')
46+
File.write('config/initializers/trackable.rb', <<~EOF)
47+
Rails.config.application.after_initialize do
48+
Synvert::User.class_eval do
49+
include Trackable
50+
end
51+
end
52+
EOF
4753

4854
rewriter.process
4955

@@ -52,29 +58,35 @@ def user_type
5258
modules: [
5359
{
5460
name: "Synvert",
61+
full_name: "Synvert",
5562
classes: [
5663
{
5764
name: "Admin",
65+
full_name: "Synvert::Admin",
5866
superclass: "User",
5967
singleton: {},
6068
classes: [],
69+
constants: [],
6170
modules: [],
6271
methods: [{ name: "user_type" }],
6372
static_methods: [],
6473
constants: [],
6574
included_modules: [],
66-
ancestors: ["User", "Trackable"]
75+
ancestors: ["Synvert::User", "Trackable"]
6776
},
6877
{
6978
name: "User",
79+
full_name: "Synvert::User",
7080
superclass: nil,
7181
singleton: {
82+
constants: [],
7283
methods: [
7384
{ name: 'system' },
7485
{ name: 'bot' }
7586
]
7687
},
7788
classes: [],
89+
constants: [],
7890
modules: [],
7991
methods: [{ name: "user_type" }],
8092
static_methods: [{ name: 'authenticate?' }],
@@ -85,10 +97,12 @@ def user_type
8597
],
8698
modules: [],
8799
methods: [],
100+
singleton: [],
88101
static_methods: [],
89102
constants: []
90103
}
91-
]
104+
],
105+
constants: []
92106
})
93107
end
94108
end

0 commit comments

Comments
 (0)