eager_eye 1.2.14 → 1.3.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 +4 -4
- data/CHANGELOG.md +47 -0
- data/README.md +227 -541
- data/README.tr.md +489 -0
- data/lib/eager_eye/baseline.rb +46 -0
- data/lib/eager_eye/cli.rb +15 -1
- data/lib/eager_eye/detectors/custom_method_query.rb +160 -12
- data/lib/eager_eye/detectors/loop_association.rb +238 -26
- data/lib/eager_eye/issue.rb +18 -6
- data/lib/eager_eye/version.rb +1 -1
- data/lib/eager_eye.rb +1 -0
- metadata +4 -2
|
@@ -4,7 +4,7 @@ require_relative "concerns/variable_model_inference"
|
|
|
4
4
|
|
|
5
5
|
module EagerEye
|
|
6
6
|
module Detectors
|
|
7
|
-
class CustomMethodQuery < Base
|
|
7
|
+
class CustomMethodQuery < Base # rubocop:disable Metrics/ClassLength
|
|
8
8
|
include Concerns::VariableModelInference
|
|
9
9
|
|
|
10
10
|
QUERY_METHODS = %i[where find_by find_by! exists? find first last take pluck ids count sum average minimum
|
|
@@ -27,30 +27,178 @@ module EagerEye
|
|
|
27
27
|
@file_path = file_path
|
|
28
28
|
@method_queries = method_queries
|
|
29
29
|
@associations_by_model = associations_by_model
|
|
30
|
-
@variable_models = {}
|
|
31
30
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
31
|
+
# Process each method def as its own scope so variable models from one
|
|
32
|
+
# method don't bleed into another (e.g. `orders = Order.all` in #index
|
|
33
|
+
# vs `orders = Foo.where(...).all` in #report — global tracking would
|
|
34
|
+
# mis-attribute the iteration variable's class across scopes).
|
|
35
|
+
process_scope(ast, {})
|
|
37
36
|
|
|
38
37
|
@issues
|
|
39
38
|
end
|
|
40
39
|
|
|
41
40
|
private
|
|
42
41
|
|
|
43
|
-
def
|
|
42
|
+
def process_scope(scope_node, definitions)
|
|
43
|
+
@variable_models ||= {}
|
|
44
|
+
scope_body = scope_body_for(scope_node)
|
|
45
|
+
return unless scope_body
|
|
46
|
+
|
|
47
|
+
find_iteration_blocks_in_scope(scope_body, definitions)
|
|
48
|
+
|
|
49
|
+
process_nested_defs(scope_body, definitions)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def process_nested_defs(scope_body, definitions)
|
|
53
|
+
nested_defs = []
|
|
54
|
+
each_nested_def(scope_body) { |d| nested_defs << d }
|
|
55
|
+
return if nested_defs.empty?
|
|
56
|
+
|
|
57
|
+
call_sites_by_callee = collect_sibling_call_sites(nested_defs)
|
|
58
|
+
|
|
59
|
+
nested_defs.each do |nested_def|
|
|
60
|
+
with_scope_snapshot do
|
|
61
|
+
seed_params_from_callers(nested_def, call_sites_by_callee)
|
|
62
|
+
process_scope(nested_def, definitions.dup)
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# See LoopAssociation#collect_sibling_call_sites for the rationale.
|
|
68
|
+
def collect_sibling_call_sites(nested_defs)
|
|
69
|
+
sibling_names = nested_defs.filter_map { |d| def_name(d) }.to_set
|
|
70
|
+
result = Hash.new { |h, k| h[k] = [] }
|
|
71
|
+
|
|
72
|
+
nested_defs.each do |def_node|
|
|
73
|
+
def_body = scope_body_for(def_node)
|
|
74
|
+
next unless def_body
|
|
75
|
+
|
|
76
|
+
with_scope_snapshot do
|
|
77
|
+
each_node_in_scope(def_body) do |node|
|
|
78
|
+
record_definition(node, {})
|
|
79
|
+
next unless self_send_to_sibling?(node, sibling_names)
|
|
80
|
+
|
|
81
|
+
result[node.children[1]] << {
|
|
82
|
+
args: node.children[2..],
|
|
83
|
+
models: @variable_models.dup
|
|
84
|
+
}
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
result
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def self_send_to_sibling?(node, sibling_names)
|
|
93
|
+
return false unless node.type == :send
|
|
94
|
+
|
|
95
|
+
receiver = node.children[0]
|
|
96
|
+
return false unless receiver.nil? || (receiver.is_a?(Parser::AST::Node) && receiver.type == :self)
|
|
97
|
+
|
|
98
|
+
sibling_names.include?(node.children[1])
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def def_name(def_node)
|
|
102
|
+
case def_node.type
|
|
103
|
+
when :def then def_node.children[0]
|
|
104
|
+
when :defs then def_node.children[1]
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def seed_params_from_callers(def_node, call_sites_by_callee)
|
|
109
|
+
return unless def_node.type == :def
|
|
110
|
+
|
|
111
|
+
call_sites = call_sites_by_callee[def_name(def_node)]
|
|
112
|
+
return if call_sites.nil? || call_sites.empty?
|
|
113
|
+
|
|
114
|
+
extract_param_names(def_node).each_with_index do |param_name, idx|
|
|
115
|
+
model = first_arg_model(call_sites, idx)
|
|
116
|
+
@variable_models[[:lvar, param_name]] = model if model
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def first_arg_model(call_sites, idx)
|
|
121
|
+
call_sites.each do |site|
|
|
122
|
+
arg = site[:args][idx]
|
|
123
|
+
next unless arg
|
|
124
|
+
|
|
125
|
+
saved = @variable_models
|
|
126
|
+
@variable_models = site[:models]
|
|
127
|
+
model = infer_model_from_value(arg)
|
|
128
|
+
@variable_models = saved
|
|
129
|
+
return model if model
|
|
130
|
+
end
|
|
131
|
+
nil
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def extract_param_names(def_node)
|
|
135
|
+
args_node = def_node.children[1]
|
|
136
|
+
return [] unless args_node.is_a?(Parser::AST::Node) && args_node.type == :args
|
|
137
|
+
|
|
138
|
+
args_node.children.filter_map do |arg|
|
|
139
|
+
next unless arg.is_a?(Parser::AST::Node) && %i[arg optarg kwarg kwoptarg].include?(arg.type)
|
|
140
|
+
|
|
141
|
+
arg.children[0]
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
def scope_body_for(node)
|
|
146
|
+
return node unless node.is_a?(Parser::AST::Node)
|
|
147
|
+
|
|
148
|
+
case node.type
|
|
149
|
+
when :def then node.children[2]
|
|
150
|
+
when :defs then node.children[3]
|
|
151
|
+
else node
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
def with_scope_snapshot
|
|
156
|
+
saved_models = @variable_models.dup
|
|
157
|
+
yield
|
|
158
|
+
ensure
|
|
159
|
+
@variable_models = saved_models
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
def each_node_in_scope(node, &block)
|
|
163
|
+
return unless node.is_a?(Parser::AST::Node)
|
|
164
|
+
|
|
165
|
+
yield node
|
|
166
|
+
|
|
167
|
+
node.children.each do |child|
|
|
168
|
+
next unless child.is_a?(Parser::AST::Node)
|
|
169
|
+
next if %i[def defs].include?(child.type)
|
|
170
|
+
|
|
171
|
+
each_node_in_scope(child, &block)
|
|
172
|
+
end
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
def each_nested_def(node, &block)
|
|
44
176
|
return unless node.is_a?(Parser::AST::Node)
|
|
45
177
|
|
|
46
|
-
|
|
178
|
+
node.children.each do |child|
|
|
179
|
+
next unless child.is_a?(Parser::AST::Node)
|
|
180
|
+
|
|
181
|
+
if %i[def defs].include?(child.type)
|
|
182
|
+
yield child
|
|
183
|
+
else
|
|
184
|
+
each_nested_def(child, &block)
|
|
185
|
+
end
|
|
186
|
+
end
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
def find_iteration_blocks_in_scope(scope_body, definitions)
|
|
190
|
+
each_node_in_scope(scope_body) do |node|
|
|
191
|
+
record_definition(node, definitions)
|
|
192
|
+
next unless iteration_block?(node)
|
|
47
193
|
|
|
48
|
-
if iteration_block?(node)
|
|
49
194
|
block_var = extract_iteration_variable(node)
|
|
50
195
|
block_body = node.children[2]
|
|
51
|
-
|
|
196
|
+
next unless block_var && block_body
|
|
197
|
+
|
|
198
|
+
model_name = infer_model_from_value(node.children[0])
|
|
199
|
+
check_block_for_query_methods(block_body, block_var, collection_is_array?(node.children[0], definitions))
|
|
200
|
+
check_block_for_model_query_methods(block_body, block_var, model_name)
|
|
52
201
|
end
|
|
53
|
-
node.children.each { |child| find_iteration_blocks(child, definitions, &block) }
|
|
54
202
|
end
|
|
55
203
|
|
|
56
204
|
def record_definition(node, definitions)
|
|
@@ -43,33 +43,22 @@ module EagerEye
|
|
|
43
43
|
method_queries = {}, associations_by_model = {})
|
|
44
44
|
return [] unless ast
|
|
45
45
|
|
|
46
|
-
issues = []
|
|
46
|
+
@issues = []
|
|
47
|
+
@file_path = file_path
|
|
47
48
|
@association_preloads = association_preloads
|
|
48
49
|
@dynamic_associations = association_names
|
|
49
50
|
@method_queries = method_queries
|
|
50
51
|
@associations_by_model = associations_by_model
|
|
51
|
-
build_variable_maps(ast)
|
|
52
52
|
|
|
53
|
-
|
|
54
|
-
|
|
53
|
+
# Variable preloads/models leak across methods if tracked globally
|
|
54
|
+
# (e.g. controller#index sets `invoices = Invoice.includes(...)`, then
|
|
55
|
+
# controller#auto_match overwrites with `invoices = Invoice.where(...)`
|
|
56
|
+
# — the second assignment would erase the first method's preload data).
|
|
57
|
+
# Process each method scope independently and inherit a snapshot from
|
|
58
|
+
# the enclosing scope (top-level / outer class body).
|
|
59
|
+
process_scope(ast)
|
|
55
60
|
|
|
56
|
-
|
|
57
|
-
next unless block_var
|
|
58
|
-
|
|
59
|
-
block_body = node.children[2]
|
|
60
|
-
next unless block_body
|
|
61
|
-
|
|
62
|
-
collection_node = node.children[0]
|
|
63
|
-
next if single_record_iteration?(collection_node)
|
|
64
|
-
|
|
65
|
-
included = collect_included_associations(collection_node)
|
|
66
|
-
model_name = infer_model_from_value(collection_node)
|
|
67
|
-
skip_nodes = collect_non_loading_skip_set(block_body)
|
|
68
|
-
|
|
69
|
-
find_association_calls(block_body, block_var, file_path, issues, included, model_name, skip_nodes)
|
|
70
|
-
end
|
|
71
|
-
|
|
72
|
-
issues
|
|
61
|
+
@issues
|
|
73
62
|
end
|
|
74
63
|
|
|
75
64
|
private
|
|
@@ -140,12 +129,235 @@ module EagerEye
|
|
|
140
129
|
included.merge(@variable_preloads[key]) if @variable_preloads&.key?(key)
|
|
141
130
|
end
|
|
142
131
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
132
|
+
# Walk this scope's body, then recurse into nested method defs as fresh
|
|
133
|
+
# scopes. A method def inherits a snapshot of the enclosing scope's
|
|
134
|
+
# variable state (so top-level lets/instance vars stay visible), but its
|
|
135
|
+
# own changes do not leak back out. Nested defs are processed in two
|
|
136
|
+
# passes: first to collect call sites between siblings (so we know which
|
|
137
|
+
# arguments each method receives at call time), then to actually analyze
|
|
138
|
+
# each def with its parameters seeded from caller context.
|
|
139
|
+
def process_scope(scope_node)
|
|
140
|
+
@variable_preloads ||= {}
|
|
141
|
+
@variable_models ||= {}
|
|
142
|
+
@single_record_variables ||= Set.new
|
|
143
|
+
|
|
144
|
+
scope_body = scope_body_for(scope_node)
|
|
145
|
+
return unless scope_body
|
|
146
|
+
|
|
147
|
+
build_variable_maps_in_scope(scope_body)
|
|
148
|
+
find_iterations_in_scope(scope_body)
|
|
149
|
+
|
|
150
|
+
process_nested_defs(scope_body)
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
def process_nested_defs(scope_body)
|
|
154
|
+
nested_defs = collect_nested_defs(scope_body)
|
|
155
|
+
return if nested_defs.empty?
|
|
156
|
+
|
|
157
|
+
call_sites_by_callee = collect_sibling_call_sites(nested_defs)
|
|
158
|
+
|
|
159
|
+
nested_defs.each do |nested_def|
|
|
160
|
+
with_scope_snapshot do
|
|
161
|
+
seed_params_from_callers(nested_def, call_sites_by_callee)
|
|
162
|
+
process_scope(nested_def)
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
def collect_nested_defs(scope_body)
|
|
168
|
+
defs = []
|
|
169
|
+
each_nested_def(scope_body) { |d| defs << d }
|
|
170
|
+
defs
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
# For each sibling def in this class/module body, build its variable map
|
|
174
|
+
# in isolation and capture every self-send to ANOTHER sibling. The
|
|
175
|
+
# captured snapshot is the caller's variable state at the call site —
|
|
176
|
+
# later we re-evaluate the call's arg expressions against this snapshot
|
|
177
|
+
# to derive the callee's parameter contexts.
|
|
178
|
+
def collect_sibling_call_sites(nested_defs)
|
|
179
|
+
sibling_names = nested_defs.filter_map { |d| def_name(d) }.to_set
|
|
180
|
+
result = Hash.new { |h, k| h[k] = [] }
|
|
181
|
+
|
|
182
|
+
nested_defs.each do |def_node|
|
|
183
|
+
def_body = scope_body_for(def_node)
|
|
184
|
+
next unless def_body
|
|
185
|
+
|
|
186
|
+
with_scope_snapshot do
|
|
187
|
+
build_variable_maps_in_scope(def_body)
|
|
188
|
+
capture_calls_to_siblings(def_body, sibling_names, result)
|
|
189
|
+
end
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
result
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
def capture_calls_to_siblings(def_body, sibling_names, result)
|
|
196
|
+
each_node_in_scope(def_body) do |node|
|
|
197
|
+
next unless self_send_to_sibling?(node, sibling_names)
|
|
198
|
+
|
|
199
|
+
callee = node.children[1]
|
|
200
|
+
result[callee] << {
|
|
201
|
+
args: node.children[2..],
|
|
202
|
+
preloads: @variable_preloads.dup,
|
|
203
|
+
models: @variable_models.dup
|
|
204
|
+
}
|
|
205
|
+
end
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
def self_send_to_sibling?(node, sibling_names)
|
|
209
|
+
return false unless node.type == :send
|
|
210
|
+
|
|
211
|
+
receiver = node.children[0]
|
|
212
|
+
return false unless receiver.nil? || (receiver.is_a?(Parser::AST::Node) && receiver.type == :self)
|
|
213
|
+
|
|
214
|
+
sibling_names.include?(node.children[1])
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
def def_name(def_node)
|
|
218
|
+
case def_node.type
|
|
219
|
+
when :def then def_node.children[0]
|
|
220
|
+
when :defs then def_node.children[1]
|
|
221
|
+
end
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
# If `def_node` is called by sibling defs in this class, evaluate each
|
|
225
|
+
# call site's arguments in that caller's context and bind the resulting
|
|
226
|
+
# preloads/model to the callee's parameter names. This is what eliminates
|
|
227
|
+
# the "helper method receives a preloaded relation" false positive.
|
|
228
|
+
def seed_params_from_callers(def_node, call_sites_by_callee)
|
|
229
|
+
return unless def_node.type == :def
|
|
230
|
+
|
|
231
|
+
name = def_name(def_node)
|
|
232
|
+
call_sites = call_sites_by_callee[name]
|
|
233
|
+
return if call_sites.nil? || call_sites.empty?
|
|
234
|
+
|
|
235
|
+
param_names = extract_param_names(def_node)
|
|
236
|
+
param_names.each_with_index do |param_name, idx|
|
|
237
|
+
seed_single_param(param_name, idx, call_sites)
|
|
238
|
+
end
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
def seed_single_param(param_name, idx, call_sites)
|
|
242
|
+
merged_preloads = Set.new
|
|
243
|
+
chosen_model = nil
|
|
244
|
+
|
|
245
|
+
call_sites.each do |site|
|
|
246
|
+
arg = site[:args][idx]
|
|
247
|
+
next unless arg
|
|
248
|
+
|
|
249
|
+
with_call_site_context(site) do
|
|
250
|
+
merged_preloads.merge(extract_included_associations_deep(arg))
|
|
251
|
+
chosen_model ||= infer_model_from_value(arg)
|
|
252
|
+
end
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
key = [:lvar, param_name]
|
|
256
|
+
@variable_preloads[key] = merged_preloads unless merged_preloads.empty?
|
|
257
|
+
@variable_models[key] = chosen_model if chosen_model
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
def with_call_site_context(site)
|
|
261
|
+
saved_preloads = @variable_preloads
|
|
262
|
+
saved_models = @variable_models
|
|
263
|
+
@variable_preloads = site[:preloads]
|
|
264
|
+
@variable_models = site[:models]
|
|
265
|
+
yield
|
|
266
|
+
ensure
|
|
267
|
+
@variable_preloads = saved_preloads
|
|
268
|
+
@variable_models = saved_models
|
|
269
|
+
end
|
|
270
|
+
|
|
271
|
+
def extract_param_names(def_node)
|
|
272
|
+
args_node = def_node.children[1]
|
|
273
|
+
return [] unless args_node.is_a?(Parser::AST::Node) && args_node.type == :args
|
|
274
|
+
|
|
275
|
+
args_node.children.filter_map do |arg|
|
|
276
|
+
next unless arg.is_a?(Parser::AST::Node)
|
|
277
|
+
# Skip blockarg/restarg/kwrestarg etc. — only positional/optional/kwarg names.
|
|
278
|
+
next unless %i[arg optarg kwarg kwoptarg].include?(arg.type)
|
|
279
|
+
|
|
280
|
+
arg.children[0]
|
|
281
|
+
end
|
|
282
|
+
end
|
|
283
|
+
|
|
284
|
+
def scope_body_for(node)
|
|
285
|
+
return node unless node.is_a?(Parser::AST::Node)
|
|
286
|
+
|
|
287
|
+
case node.type
|
|
288
|
+
when :def then node.children[2]
|
|
289
|
+
when :defs then node.children[3]
|
|
290
|
+
else node
|
|
291
|
+
end
|
|
292
|
+
end
|
|
293
|
+
|
|
294
|
+
def with_scope_snapshot
|
|
295
|
+
saved_preloads = @variable_preloads.dup
|
|
296
|
+
saved_models = @variable_models.dup
|
|
297
|
+
saved_single = @single_record_variables.dup
|
|
298
|
+
yield
|
|
299
|
+
ensure
|
|
300
|
+
@variable_preloads = saved_preloads
|
|
301
|
+
@variable_models = saved_models
|
|
302
|
+
@single_record_variables = saved_single
|
|
303
|
+
end
|
|
304
|
+
|
|
305
|
+
# Yields every node inside `scope_body` but stops at any :def/:defs —
|
|
306
|
+
# those subtrees represent fresh scopes and are visited separately.
|
|
307
|
+
def each_node_in_scope(node, &block)
|
|
308
|
+
return unless node.is_a?(Parser::AST::Node)
|
|
309
|
+
|
|
310
|
+
yield node
|
|
311
|
+
|
|
312
|
+
node.children.each do |child|
|
|
313
|
+
next unless child.is_a?(Parser::AST::Node)
|
|
314
|
+
next if %i[def defs].include?(child.type)
|
|
315
|
+
|
|
316
|
+
each_node_in_scope(child, &block)
|
|
317
|
+
end
|
|
318
|
+
end
|
|
319
|
+
|
|
320
|
+
# Yields each immediately-nested :def/:defs (not deeper-nested ones —
|
|
321
|
+
# those are visited via that def's own process_scope call).
|
|
322
|
+
def each_nested_def(node, &block)
|
|
323
|
+
return unless node.is_a?(Parser::AST::Node)
|
|
324
|
+
|
|
325
|
+
node.children.each do |child|
|
|
326
|
+
next unless child.is_a?(Parser::AST::Node)
|
|
327
|
+
|
|
328
|
+
if %i[def defs].include?(child.type)
|
|
329
|
+
yield child
|
|
330
|
+
else
|
|
331
|
+
each_nested_def(child, &block)
|
|
332
|
+
end
|
|
333
|
+
end
|
|
334
|
+
end
|
|
335
|
+
|
|
336
|
+
def build_variable_maps_in_scope(scope_body)
|
|
337
|
+
each_node_in_scope(scope_body) { |node| process_variable_assignment(node) }
|
|
338
|
+
end
|
|
339
|
+
|
|
340
|
+
def find_iterations_in_scope(scope_body)
|
|
341
|
+
each_node_in_scope(scope_body) do |node|
|
|
342
|
+
process_iteration_block(node) if iteration_block?(node)
|
|
343
|
+
end
|
|
344
|
+
end
|
|
345
|
+
|
|
346
|
+
def process_iteration_block(node)
|
|
347
|
+
block_var = extract_iteration_variable(node)
|
|
348
|
+
return unless block_var
|
|
349
|
+
|
|
350
|
+
block_body = node.children[2]
|
|
351
|
+
return unless block_body
|
|
352
|
+
|
|
353
|
+
collection_node = node.children[0]
|
|
354
|
+
return if single_record_iteration?(collection_node)
|
|
355
|
+
|
|
356
|
+
included = collect_included_associations(collection_node)
|
|
357
|
+
model_name = infer_model_from_value(collection_node)
|
|
358
|
+
skip_nodes = collect_non_loading_skip_set(block_body)
|
|
147
359
|
|
|
148
|
-
|
|
360
|
+
find_association_calls(block_body, block_var, @file_path, @issues, included, model_name, skip_nodes)
|
|
149
361
|
end
|
|
150
362
|
|
|
151
363
|
def process_variable_assignment(node)
|
data/lib/eager_eye/issue.rb
CHANGED
|
@@ -16,6 +16,18 @@ module EagerEye
|
|
|
16
16
|
@suggestion = suggestion
|
|
17
17
|
end
|
|
18
18
|
|
|
19
|
+
def self.from_h(hash)
|
|
20
|
+
h = hash.transform_keys(&:to_sym)
|
|
21
|
+
new(
|
|
22
|
+
detector: h.fetch(:detector).to_sym,
|
|
23
|
+
file_path: h.fetch(:file_path),
|
|
24
|
+
line_number: h.fetch(:line_number),
|
|
25
|
+
message: h.fetch(:message),
|
|
26
|
+
severity: (h[:severity] || :warning).to_sym,
|
|
27
|
+
suggestion: h[:suggestion]
|
|
28
|
+
)
|
|
29
|
+
end
|
|
30
|
+
|
|
19
31
|
def severity_level
|
|
20
32
|
SEVERITY_ORDER[severity]
|
|
21
33
|
end
|
|
@@ -26,12 +38,12 @@ module EagerEye
|
|
|
26
38
|
|
|
27
39
|
def to_h
|
|
28
40
|
{
|
|
29
|
-
detector
|
|
30
|
-
file_path
|
|
31
|
-
line_number
|
|
32
|
-
message
|
|
33
|
-
severity
|
|
34
|
-
suggestion:
|
|
41
|
+
detector:,
|
|
42
|
+
file_path:,
|
|
43
|
+
line_number:,
|
|
44
|
+
message:,
|
|
45
|
+
severity:,
|
|
46
|
+
suggestion:
|
|
35
47
|
}
|
|
36
48
|
end
|
|
37
49
|
|
data/lib/eager_eye/version.rb
CHANGED
data/lib/eager_eye.rb
CHANGED
|
@@ -4,6 +4,7 @@ require "set"
|
|
|
4
4
|
require_relative "eager_eye/version"
|
|
5
5
|
require_relative "eager_eye/configuration"
|
|
6
6
|
require_relative "eager_eye/issue"
|
|
7
|
+
require_relative "eager_eye/baseline"
|
|
7
8
|
require_relative "eager_eye/association_parser"
|
|
8
9
|
require_relative "eager_eye/delegation_parser"
|
|
9
10
|
require_relative "eager_eye/scope_parser"
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: eager_eye
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.3.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- hamzagedikkaya
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-05-
|
|
11
|
+
date: 2026-05-20 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: ast
|
|
@@ -54,6 +54,7 @@ files:
|
|
|
54
54
|
- CONTRIBUTING.md
|
|
55
55
|
- LICENSE.txt
|
|
56
56
|
- README.md
|
|
57
|
+
- README.tr.md
|
|
57
58
|
- Rakefile
|
|
58
59
|
- SECURITY.md
|
|
59
60
|
- exe/eager_eye
|
|
@@ -61,6 +62,7 @@ files:
|
|
|
61
62
|
- lib/eager_eye/analyzer.rb
|
|
62
63
|
- lib/eager_eye/association_parser.rb
|
|
63
64
|
- lib/eager_eye/auto_fixer.rb
|
|
65
|
+
- lib/eager_eye/baseline.rb
|
|
64
66
|
- lib/eager_eye/cli.rb
|
|
65
67
|
- lib/eager_eye/comment_parser.rb
|
|
66
68
|
- lib/eager_eye/configuration.rb
|