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.
@@ -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