-
Notifications
You must be signed in to change notification settings - Fork 738
/
Copy pathcall_index.rb
261 lines (214 loc) · 6.1 KB
/
call_index.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
require 'set'
#Stores call sites to look up later.
class Brakeman::CallIndex
#Initialize index with calls from FindAllCalls
def initialize calls
@calls_by_method = {}
@calls_by_target = {}
index_calls calls
end
#Find calls matching specified option hash.
#
#Options:
#
# * :target - symbol, array of symbols, or regular expression to match target(s)
# * :method - symbol, array of symbols, or regular expression to match method(s)
# * :chained - boolean, whether or not to match against a whole method chain (false by default)
# * :nested - boolean, whether or not to match against a method call that is a target itself (false by default)
def find_calls options
target = options[:target] || options[:targets]
method = options[:method] || options[:methods]
nested = options[:nested]
if options[:chained]
return find_chain options
#Find by narrowest category
elsif target.is_a? Array and method.is_a? Array
if target.length > method.length
calls = filter_by_target calls_by_methods(method), target
else
calls = calls_by_targets(target)
calls = filter_by_method calls, method
end
elsif target.is_a? Regexp and method
calls = filter_by_target(calls_by_method(method), target)
elsif method.is_a? Regexp and target
calls = filter_by_method(calls_by_target(target), method)
#Find by target, then by methods, if provided
elsif target
calls = calls_by_target target
if calls and method
calls = filter_by_method calls, method
end
#Find calls with no explicit target
#with either :target => nil or :target => false
elsif (options.key? :target or options.key? :targets) and not target and method
calls = calls_by_method method
calls = filter_by_target calls, nil
#Find calls by method
elsif method
calls = calls_by_method method
else
raise "Invalid arguments to CallCache#find_calls: #{options.inspect}"
end
return [] if calls.nil?
#Remove calls that are actually targets of other calls
#Unless those are explicitly desired
calls = filter_nested calls unless nested
calls
end
def remove_template_indexes template_name = nil
[@calls_by_method, @calls_by_target].each do |calls_by|
calls_by.each do |_name, calls|
calls.delete_if do |call|
from_template call, template_name
end
end
end
end
def remove_indexes_by_class classes
[@calls_by_method, @calls_by_target].each do |calls_by|
calls_by.each do |_name, calls|
calls.delete_if do |call|
call[:location][:type] == :class and classes.include? call[:location][:class]
end
end
end
end
def remove_indexes_by_file file
[@calls_by_method, @calls_by_target].each do |calls_by|
calls_by.each do |_name, calls|
calls.delete_if do |call|
call[:location][:file] == file
end
end
end
end
def index_calls calls
calls.each do |call|
@calls_by_method[call[:method]] ||= []
@calls_by_method[call[:method]] << call
target = call[:target]
if not target.is_a? Sexp
@calls_by_target[target] ||= []
@calls_by_target[target] << call
elsif target.node_type == :params or target.node_type == :session
@calls_by_target[target.node_type] ||= []
@calls_by_target[target.node_type] << call
end
end
end
private
def find_chain options
target = options[:target] || options[:targets]
method = options[:method] || options[:methods]
calls = calls_by_method method
return [] if calls.nil?
calls = filter_by_chain calls, target
end
def calls_by_target target
case target
when Array
calls_by_targets target
when Regexp
calls_by_targets_regex target
else
@calls_by_target[target] || []
end
end
def calls_by_targets targets
calls = []
targets.each do |target|
calls.concat @calls_by_target[target] if @calls_by_target.key? target
end
calls
end
def calls_by_targets_regex targets_regex
calls = []
@calls_by_target.each do |key, value|
case key
when String, Symbol
calls.concat value if key.match targets_regex
end
end
calls
end
def calls_by_method method
case method
when Array
calls_by_methods method
when Regexp
calls_by_methods_regex method
else
@calls_by_method[method.to_sym] || []
end
end
def calls_by_methods methods
methods = methods.map { |m| m.to_sym }
calls = []
methods.each do |method|
calls.concat @calls_by_method[method] if @calls_by_method.key? method
end
calls
end
def calls_by_methods_regex methods_regex
calls = []
@calls_by_method.each do |key, value|
calls.concat value if key.match methods_regex
end
calls
end
def filter calls, key, value
case value
when Array
values = Set.new value
calls.select do |call|
values.include? call[key]
end
when Regexp
calls.select do |call|
case call[key]
when String, Symbol
call[key].match value
end
end
else
calls.select do |call|
call[key] == value
end
end
end
def filter_by_method calls, method
filter calls, :method, method
end
def filter_by_target calls, target
filter calls, :target, target
end
def filter_nested calls
filter calls, :nested, false
end
def filter_by_chain calls, target
case target
when Array
targets = Set.new target
calls.select do |call|
targets.include? call[:chain].first
end
when Regexp
calls.select do |call|
case call[:chain].first
when String, Symbol
call[:chain].first.match target
end
end
else
calls.select do |call|
call[:chain].first == target
end
end
end
def from_template call, template_name
return false unless call[:location][:type] == :template
return true if template_name.nil?
call[:location][:template] == template_name
end
end