system_navigation 0.1.0
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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +6 -0
- data/LICENCE.txt +19 -0
- data/README.md +70 -0
- data/VERSION +1 -0
- data/lib/system_navigation.rb +374 -0
- data/lib/system_navigation/ancestor_method_finder.rb +44 -0
- data/lib/system_navigation/array_refinement.rb +21 -0
- data/lib/system_navigation/compiled_method.rb +94 -0
- data/lib/system_navigation/expression_tree.rb +95 -0
- data/lib/system_navigation/instruction_stream.rb +33 -0
- data/lib/system_navigation/instruction_stream/decoder.rb +91 -0
- data/lib/system_navigation/instruction_stream/instruction.rb +180 -0
- data/lib/system_navigation/instruction_stream/instruction/attr_instruction.rb +90 -0
- data/lib/system_navigation/method_hash.rb +49 -0
- data/lib/system_navigation/method_query.rb +98 -0
- data/lib/system_navigation/module_refinement.rb +226 -0
- data/lib/system_navigation/ruby_environment.rb +75 -0
- metadata +117 -0
@@ -0,0 +1,90 @@
|
|
1
|
+
class SystemNavigation
|
2
|
+
class InstructionStream
|
3
|
+
class Instruction
|
4
|
+
class AttrInstruction < Instruction
|
5
|
+
def self.parse(method, sym)
|
6
|
+
instr = self.new(method)
|
7
|
+
self.attrreaderinstr(instr, sym) || self.attrwriterinstr(instr, sym)
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.attrreaderinstr(instr, sym)
|
11
|
+
instr.accept(AttrReaderInstruction.new(sym)) && instr.parse
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.attrwriterinstr(instr, sym)
|
15
|
+
instr.accept(AttrWriterInstruction.new(sym)) && instr.parse
|
16
|
+
end
|
17
|
+
|
18
|
+
attr_accessor :visitor
|
19
|
+
attr_reader :method
|
20
|
+
|
21
|
+
def initialize(method)
|
22
|
+
@method = method
|
23
|
+
end
|
24
|
+
|
25
|
+
def accept(visitor)
|
26
|
+
visitor.visit(self)
|
27
|
+
end
|
28
|
+
|
29
|
+
def parse
|
30
|
+
[self.visitor]
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def convert_accessor_to_name(sym)
|
36
|
+
sym.to_s.tr('@', '').downcase
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
class AttrReaderInstruction < AttrInstruction
|
41
|
+
def initialize(sym)
|
42
|
+
@sym = sym
|
43
|
+
end
|
44
|
+
|
45
|
+
def visit(obj)
|
46
|
+
matched = obj.method.original_name.to_s == convert_accessor_to_name(@sym)
|
47
|
+
obj.visitor = self if matched
|
48
|
+
matched
|
49
|
+
end
|
50
|
+
|
51
|
+
def reads_ivar?(_sym)
|
52
|
+
true
|
53
|
+
end
|
54
|
+
|
55
|
+
def writes_ivar?(_sym)
|
56
|
+
false
|
57
|
+
end
|
58
|
+
|
59
|
+
def putobjects?(sym)
|
60
|
+
@sym == sym
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
class AttrWriterInstruction < AttrInstruction
|
65
|
+
def initialize(sym)
|
66
|
+
@sym = sym
|
67
|
+
end
|
68
|
+
|
69
|
+
def visit(obj)
|
70
|
+
name = obj.method.original_name.to_s
|
71
|
+
matched = (name[-1] == '=') && (name[0..-2] == convert_accessor_to_name(@sym))
|
72
|
+
obj.visitor = self if matched
|
73
|
+
matched
|
74
|
+
end
|
75
|
+
|
76
|
+
def reads_ivar?(_sym)
|
77
|
+
false
|
78
|
+
end
|
79
|
+
|
80
|
+
def writes_ivar?(_sym)
|
81
|
+
true
|
82
|
+
end
|
83
|
+
|
84
|
+
def putobjects?(sym)
|
85
|
+
@sym == sym
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
class SystemNavigation
|
2
|
+
class MethodHash < Hash
|
3
|
+
def self.create(**args)
|
4
|
+
self.new(args)
|
5
|
+
end
|
6
|
+
|
7
|
+
|
8
|
+
def initialize(based_on: nil, include_super: nil)
|
9
|
+
@hash = super()
|
10
|
+
|
11
|
+
if based_on
|
12
|
+
@hash.merge!({
|
13
|
+
public: {
|
14
|
+
instance: based_on.public_instance_methods(include_super),
|
15
|
+
singleton: based_on.singleton_class.public_instance_methods(include_super)
|
16
|
+
},
|
17
|
+
private: {
|
18
|
+
instance: based_on.private_instance_methods(include_super),
|
19
|
+
singleton: based_on.singleton_class.private_instance_methods(include_super)
|
20
|
+
},
|
21
|
+
protected: {
|
22
|
+
instance: based_on.protected_instance_methods(include_super),
|
23
|
+
singleton: based_on.singleton_class.protected_instance_methods(include_super)
|
24
|
+
}
|
25
|
+
})
|
26
|
+
else
|
27
|
+
@hash.merge!(empty_hash)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def as_array
|
32
|
+
self.values.map { |h| h[:instance] + h[:singleton] }.flatten.compact
|
33
|
+
end
|
34
|
+
|
35
|
+
def empty?
|
36
|
+
self == empty_hash
|
37
|
+
end
|
38
|
+
|
39
|
+
protected
|
40
|
+
|
41
|
+
def empty_hash
|
42
|
+
{
|
43
|
+
public: {instance: [], singleton: []},
|
44
|
+
private: {instance: [], singleton: []},
|
45
|
+
protected: {instance: [], singleton: []}
|
46
|
+
}
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
class SystemNavigation
|
2
|
+
class MethodQuery
|
3
|
+
def self.execute(collection:, query:, behavior: nil, **rest)
|
4
|
+
args = [query]
|
5
|
+
args << rest unless rest.empty?
|
6
|
+
self.new(collection, behavior).__send__(*args)
|
7
|
+
end
|
8
|
+
|
9
|
+
def initialize(collection, behavior)
|
10
|
+
@collection = collection
|
11
|
+
@behavior = behavior
|
12
|
+
end
|
13
|
+
|
14
|
+
def convert_to_methods
|
15
|
+
self.instance_and_singleton_do(
|
16
|
+
for_instance: proc { |_scope, _selectors, selector|
|
17
|
+
@behavior.instance_method(selector)
|
18
|
+
},
|
19
|
+
|
20
|
+
for_singleton: proc { |_scope, _selectors, selector|
|
21
|
+
@behavior.singleton_class.instance_method(selector)
|
22
|
+
}
|
23
|
+
)
|
24
|
+
end
|
25
|
+
|
26
|
+
def tupleize
|
27
|
+
self.instance_and_singleton_do(
|
28
|
+
for_all: proc { |_scope, _selectors, method| [method.name, method] }
|
29
|
+
)
|
30
|
+
end
|
31
|
+
|
32
|
+
def find_literal(literal:)
|
33
|
+
self.instance_and_singleton_do(
|
34
|
+
for_all: proc { |_scope, _selectors, method|
|
35
|
+
compiled_method = CompiledMethod.compile(method)
|
36
|
+
compiled_method.unwrap if compiled_method.has_literal?(literal)
|
37
|
+
}
|
38
|
+
)
|
39
|
+
end
|
40
|
+
|
41
|
+
def all_in_the_same_file?
|
42
|
+
self.instance_and_singleton_do(
|
43
|
+
for_all: proc { |_scope, _selectors, method| method.source_location[0] }
|
44
|
+
).as_array.uniq.count == 1
|
45
|
+
end
|
46
|
+
|
47
|
+
def find_accessing_methods(ivar:, only_set:, only_get:)
|
48
|
+
self.instance_and_singleton_do(
|
49
|
+
for_all: proc { |_scope, _selectors, method|
|
50
|
+
compiled_method = CompiledMethod.compile(method)
|
51
|
+
if only_set
|
52
|
+
compiled_method.unwrap if compiled_method.writes_field?(ivar)
|
53
|
+
elsif only_get
|
54
|
+
compiled_method.unwrap if compiled_method.reads_field?(ivar)
|
55
|
+
else
|
56
|
+
if compiled_method.reads_field?(ivar) ||
|
57
|
+
compiled_method.writes_field?(ivar)
|
58
|
+
compiled_method.unwrap
|
59
|
+
end
|
60
|
+
end
|
61
|
+
}
|
62
|
+
)
|
63
|
+
end
|
64
|
+
|
65
|
+
def select_sent_messages
|
66
|
+
self.instance_and_singleton_do(
|
67
|
+
for_all: proc { |_scope, _selectors, method|
|
68
|
+
compiled_method = CompiledMethod.compile(method)
|
69
|
+
compiled_method.sent_messages.uniq.map(&:to_sym)
|
70
|
+
}
|
71
|
+
)
|
72
|
+
end
|
73
|
+
|
74
|
+
protected
|
75
|
+
|
76
|
+
def instance_and_singleton_do(for_instance: nil, for_singleton: nil, for_all: nil)
|
77
|
+
for_instance = for_all && for_singleton = for_all if for_all
|
78
|
+
|
79
|
+
@collection.inject(MethodHash.new) do |h, (scope, selectors)|
|
80
|
+
self.evaluate(callable: for_instance, group: :instance, hash: h,
|
81
|
+
scope: scope, selectors: selectors)
|
82
|
+
self.evaluate(callable: for_singleton, group: :singleton, hash: h,
|
83
|
+
scope: scope, selectors: selectors)
|
84
|
+
h
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def evaluate(callable:, group:, hash:, scope:, selectors:)
|
89
|
+
return if callable.nil?
|
90
|
+
|
91
|
+
result = selectors[group].map do |selector|
|
92
|
+
callable.call(scope, selectors, selector)
|
93
|
+
end
|
94
|
+
|
95
|
+
hash[scope][group].concat(result)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
@@ -0,0 +1,226 @@
|
|
1
|
+
class SystemNavigation
|
2
|
+
module ModuleRefinement
|
3
|
+
refine Module do
|
4
|
+
def with_all_sub_and_superclasses
|
5
|
+
Enumerator.new do |y|
|
6
|
+
self.with_all_subclasses.each { |klass| y << klass }
|
7
|
+
self.with_all_superclasses.each { |klass| y << klass }
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
def with_all_subclasses
|
12
|
+
Enumerator.new do |y|
|
13
|
+
self.all_subclasses.push(self).each { |subclass| y << subclass }
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def all_subclasses
|
18
|
+
all_subclasses = []
|
19
|
+
|
20
|
+
ObjectSpace.each_object(self.singleton_class) do |klass|
|
21
|
+
all_subclasses.unshift(klass) if klass != self
|
22
|
+
end
|
23
|
+
|
24
|
+
all_subclasses
|
25
|
+
end
|
26
|
+
|
27
|
+
def with_all_superclasses
|
28
|
+
if self.superclass
|
29
|
+
Enumerator.new do |y|
|
30
|
+
y.yield self.superclass
|
31
|
+
self.superclass.with_all_superclasses.each { |klass| y << klass }
|
32
|
+
end
|
33
|
+
else
|
34
|
+
[]
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def select_methods_that_access(ivar, only_get, only_set)
|
39
|
+
own_methods = self.own_methods
|
40
|
+
if ancestor_methods.any?
|
41
|
+
ancestor_methods.each do |methods|
|
42
|
+
own_methods.merge!(methods) do |_group, old_h, new_h|
|
43
|
+
old_h.merge!(new_h) { |_key, oldval, newval| oldval | newval }
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
MethodQuery.execute(
|
49
|
+
collection: own_methods,
|
50
|
+
query: :find_accessing_methods,
|
51
|
+
ivar: ivar,
|
52
|
+
only_get: only_get,
|
53
|
+
only_set: only_set,
|
54
|
+
behavior: self).as_array
|
55
|
+
end
|
56
|
+
|
57
|
+
def select_methods_that_refer_to(literal)
|
58
|
+
MethodQuery.execute(
|
59
|
+
collection: self.own_methods,
|
60
|
+
query: :find_literal,
|
61
|
+
literal: literal,
|
62
|
+
behavior: self).as_array
|
63
|
+
end
|
64
|
+
|
65
|
+
def reachable_selectors
|
66
|
+
MethodHash.create(based_on: self, include_super: true)
|
67
|
+
end
|
68
|
+
|
69
|
+
def own_selectors
|
70
|
+
MethodHash.create(based_on: self, include_super: false)
|
71
|
+
end
|
72
|
+
|
73
|
+
def reachable_methods
|
74
|
+
MethodQuery.execute(
|
75
|
+
collection: self.reachable_selectors,
|
76
|
+
query: :convert_to_methods,
|
77
|
+
behavior: self)
|
78
|
+
end
|
79
|
+
|
80
|
+
def own_methods
|
81
|
+
MethodQuery.execute(
|
82
|
+
collection: self.own_selectors,
|
83
|
+
query: :convert_to_methods,
|
84
|
+
behavior: self)
|
85
|
+
end
|
86
|
+
|
87
|
+
def reachable_method_hash
|
88
|
+
MethodQuery.execute(
|
89
|
+
collection: self.reachable_methods,
|
90
|
+
query: :tupleize)
|
91
|
+
end
|
92
|
+
|
93
|
+
def own_method_hash
|
94
|
+
MethodQuery.execute(
|
95
|
+
collection: self.own_methods,
|
96
|
+
query: :tupleize)
|
97
|
+
end
|
98
|
+
|
99
|
+
def which_global_selectors_refer_to(literal)
|
100
|
+
who = []
|
101
|
+
|
102
|
+
self.own_selectors_and_methods do |selector, method|
|
103
|
+
if method.has_literal?(literal)
|
104
|
+
who << selector
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
who
|
109
|
+
end
|
110
|
+
|
111
|
+
def belongs_to?(gem_name)
|
112
|
+
gemspec = Gem::Specification.find_all_by_name(gem_name).last
|
113
|
+
|
114
|
+
return false if gemspec.nil? || self.own_selectors.empty?
|
115
|
+
|
116
|
+
pattern = %r{(?:/gems/#{gem_name}-#{gemspec.version}/)|(?:/lib/ruby/[[0-9]\.]+/#{gem_name}/)}
|
117
|
+
match_location = proc { |locations|
|
118
|
+
!!locations.max_by { |_k, value| value }[0].match(pattern)
|
119
|
+
}
|
120
|
+
|
121
|
+
if self.contains_only_rb_methods?
|
122
|
+
if self.all_neighbour_methods?
|
123
|
+
self.own_methods.as_array.all? do |method|
|
124
|
+
method.source_location.first.match(pattern)
|
125
|
+
end
|
126
|
+
else
|
127
|
+
grouped_locations = self.group_locations_by_path
|
128
|
+
return false if grouped_locations.empty?
|
129
|
+
|
130
|
+
if grouped_locations.all? { |l| l[0].match(pattern) }
|
131
|
+
true
|
132
|
+
else
|
133
|
+
match_location.call(grouped_locations)
|
134
|
+
end
|
135
|
+
end
|
136
|
+
else
|
137
|
+
grouped_locations = self.group_locations_by_path
|
138
|
+
grouped_locations.delete_if { |k, v| k.nil? }
|
139
|
+
|
140
|
+
if grouped_locations.empty?
|
141
|
+
false
|
142
|
+
else
|
143
|
+
match_location.call(grouped_locations)
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
def contains_only_rb_methods?
|
149
|
+
self.own_methods.as_array.all? { |method| method.source_location }
|
150
|
+
end
|
151
|
+
|
152
|
+
def all_neighbour_methods?
|
153
|
+
MethodQuery.execute(
|
154
|
+
collection: self.own_methods,
|
155
|
+
query: :all_in_the_same_file?)
|
156
|
+
end
|
157
|
+
|
158
|
+
def group_locations_by_path
|
159
|
+
Hash[
|
160
|
+
self.own_methods.as_array.map do |method|
|
161
|
+
method.source_location && method.source_location.first || nil
|
162
|
+
end.group_by(&:itself).map do |key, value|
|
163
|
+
[key, value.count]
|
164
|
+
end.reject { |k, _v| k.nil? }
|
165
|
+
]
|
166
|
+
end
|
167
|
+
|
168
|
+
def select_matching_methods(string, match_case)
|
169
|
+
self.own_methods.as_array.select do |method|
|
170
|
+
compiled_method = CompiledMethod.compile(method)
|
171
|
+
if compiled_method.source_contains?(string, match_case)
|
172
|
+
compiled_method.unwrap
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
def select_c_methods
|
178
|
+
self.own_methods.as_array.select do |method|
|
179
|
+
compiled_method = CompiledMethod.compile(method)
|
180
|
+
if compiled_method.c_method?
|
181
|
+
compiled_method.unwrap
|
182
|
+
end
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
def select_rb_methods
|
187
|
+
self.own_methods.as_array.select do |method|
|
188
|
+
compiled_method = CompiledMethod.compile(method)
|
189
|
+
if compiled_method.rb_method?
|
190
|
+
compiled_method.unwrap
|
191
|
+
end
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
def select_senders_of(message)
|
196
|
+
self.own_methods.as_array.select do |method|
|
197
|
+
compiled_method = CompiledMethod.compile(method)
|
198
|
+
if compiled_method.sends_message?(message)
|
199
|
+
compiled_method.unwrap
|
200
|
+
end
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
def all_messages
|
205
|
+
MethodQuery.execute(
|
206
|
+
collection: self.own_methods,
|
207
|
+
query: :select_sent_messages)
|
208
|
+
end
|
209
|
+
|
210
|
+
def which_selectors_store_into(ivar)
|
211
|
+
self.selectors.select do |sel|
|
212
|
+
meth = self.instance_method(sel)
|
213
|
+
meth.writes_field?(ivar)
|
214
|
+
end
|
215
|
+
end
|
216
|
+
|
217
|
+
def ancestor_methods
|
218
|
+
AncestorMethodFinder.find_all_ancestors(of: self)
|
219
|
+
end
|
220
|
+
|
221
|
+
def includes_selector?(selector)
|
222
|
+
self.own_selectors.as_array.include?(selector)
|
223
|
+
end
|
224
|
+
end
|
225
|
+
end
|
226
|
+
end
|