eager_eye 1.1.0 → 1.1.1
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/.rubocop.yml +1 -1
- data/CHANGELOG.md +8 -0
- data/README.md +1 -1
- data/SECURITY.md +1 -0
- data/lib/eager_eye/analyzer.rb +5 -14
- data/lib/eager_eye/association_parser.rb +6 -28
- data/lib/eager_eye/auto_fixer.rb +4 -12
- data/lib/eager_eye/cli.rb +2 -7
- data/lib/eager_eye/comment_parser.rb +0 -5
- data/lib/eager_eye/detectors/base.rb +3 -10
- data/lib/eager_eye/detectors/callback_query.rb +11 -45
- data/lib/eager_eye/detectors/count_in_iteration.rb +16 -54
- data/lib/eager_eye/detectors/custom_method_query.rb +25 -91
- data/lib/eager_eye/detectors/loop_association.rb +7 -30
- data/lib/eager_eye/detectors/missing_counter_cache.rb +16 -47
- data/lib/eager_eye/detectors/pluck_to_array.rb +15 -48
- data/lib/eager_eye/detectors/serializer_nesting.rb +20 -70
- data/lib/eager_eye/fixers/pluck_to_select.rb +2 -9
- data/lib/eager_eye/issue.rb +2 -9
- data/lib/eager_eye/railtie.rb +7 -20
- data/lib/eager_eye/reporters/console.rb +7 -18
- data/lib/eager_eye/rspec/matchers.rb +1 -6
- data/lib/eager_eye/version.rb +1 -1
- data/lib/eager_eye.rb +0 -1
- data/sig/eager_eye.rbs +0 -1
- metadata +2 -2
|
@@ -5,11 +5,8 @@ module EagerEye
|
|
|
5
5
|
class LoopAssociation < Base
|
|
6
6
|
ITERATION_METHODS = %i[each map collect select find find_all reject filter filter_map flat_map].freeze
|
|
7
7
|
PRELOAD_METHODS = %i[includes preload eager_load].freeze
|
|
8
|
-
# Methods that return a single record (not a collection)
|
|
9
8
|
SINGLE_RECORD_METHODS = %i[find find_by find_by! first first! last last! take take! second third fourth fifth
|
|
10
9
|
forty_two sole find_sole_by].freeze
|
|
11
|
-
|
|
12
|
-
# Common association names (belongs_to = singular, has_many = plural)
|
|
13
10
|
ASSOCIATION_NAMES = Set.new(%w[
|
|
14
11
|
author user owner creator admin member customer client post article comment category tag
|
|
15
12
|
parent company organization project task item order product account profile setting image
|
|
@@ -18,8 +15,6 @@ module EagerEye
|
|
|
18
15
|
tasks items orders products accounts profiles settings images avatars photos attachments
|
|
19
16
|
documents
|
|
20
17
|
]).freeze
|
|
21
|
-
|
|
22
|
-
# Methods that should NOT be treated as associations
|
|
23
18
|
EXCLUDED_METHODS = %i[
|
|
24
19
|
id to_s to_h to_a to_json to_xml inspect class object_id nil? blank? present? empty?
|
|
25
20
|
any? none? size count length save save! update update! destroy destroy! delete delete!
|
|
@@ -73,46 +68,32 @@ module EagerEye
|
|
|
73
68
|
end
|
|
74
69
|
|
|
75
70
|
def infer_model_name_from_collection(node)
|
|
76
|
-
# Infer model name from collection (posts -> Post, users -> User)
|
|
77
71
|
return nil unless node&.type == :send
|
|
78
72
|
|
|
79
|
-
# Handle: Model.includes, @var.method calls
|
|
80
73
|
receiver = node.children[0]
|
|
81
|
-
|
|
82
|
-
when :const
|
|
83
|
-
receiver.children[1].to_s
|
|
84
|
-
end
|
|
74
|
+
receiver.children[1].to_s if receiver&.type == :const
|
|
85
75
|
end
|
|
86
76
|
|
|
87
77
|
def iteration_block?(node)
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
send_node = node.children[0]
|
|
91
|
-
return false unless send_node&.type == :send
|
|
92
|
-
|
|
93
|
-
method_name = send_node.children[1]
|
|
94
|
-
ITERATION_METHODS.include?(method_name)
|
|
78
|
+
node.type == :block && node.children[0]&.type == :send &&
|
|
79
|
+
ITERATION_METHODS.include?(node.children[0].children[1])
|
|
95
80
|
end
|
|
96
81
|
|
|
97
82
|
def extract_block_variable(block_node)
|
|
98
|
-
args = block_node&.children&.
|
|
99
|
-
|
|
100
|
-
|
|
83
|
+
args = block_node&.children&.[](1)
|
|
84
|
+
first_arg = args&.children&.first
|
|
85
|
+
first_arg&.type == :arg ? first_arg.children[0] : nil
|
|
101
86
|
end
|
|
102
87
|
|
|
103
88
|
def extract_included_associations(collection_node)
|
|
104
89
|
included = Set.new
|
|
105
90
|
return included unless collection_node&.type == :send
|
|
106
91
|
|
|
107
|
-
# Traverse through chained method calls to find includes/preload/eager_load
|
|
108
92
|
current = collection_node
|
|
109
93
|
while current&.type == :send
|
|
110
|
-
|
|
111
|
-
extract_includes_from_method(current, included) if PRELOAD_METHODS.include?(method_name)
|
|
112
|
-
|
|
94
|
+
extract_includes_from_method(current, included) if PRELOAD_METHODS.include?(current.children[1])
|
|
113
95
|
current = current.children[0]
|
|
114
96
|
end
|
|
115
|
-
|
|
116
97
|
included
|
|
117
98
|
end
|
|
118
99
|
|
|
@@ -177,10 +158,6 @@ module EagerEye
|
|
|
177
158
|
included_set.merge(extract_symbols_from_args(args))
|
|
178
159
|
end
|
|
179
160
|
|
|
180
|
-
def extract_from_hash(hash_node, included_set)
|
|
181
|
-
extract_symbols_from_hash(hash_node, included_set)
|
|
182
|
-
end
|
|
183
|
-
|
|
184
161
|
def find_association_calls(node, block_var, file_path, issues, included_associations = Set.new)
|
|
185
162
|
reported = Set.new
|
|
186
163
|
traverse_ast(node) do |child|
|
|
@@ -3,22 +3,14 @@
|
|
|
3
3
|
module EagerEye
|
|
4
4
|
module Detectors
|
|
5
5
|
class MissingCounterCache < Base
|
|
6
|
-
# Methods that trigger COUNT queries
|
|
7
6
|
COUNT_METHODS = %i[count size length].freeze
|
|
8
|
-
|
|
9
|
-
# Common has_many association names (plural)
|
|
10
7
|
PLURAL_ASSOCIATIONS = %w[
|
|
11
|
-
posts comments tags categories articles users members
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
followers followings likes favorites bookmarks votes
|
|
15
|
-
children replies responses answers questions
|
|
8
|
+
posts comments tags categories articles users members items orders products tasks projects
|
|
9
|
+
images attachments documents files messages notifications reviews ratings followers followings
|
|
10
|
+
likes favorites bookmarks votes children replies responses answers questions
|
|
16
11
|
].freeze
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
ITERATION_METHODS = %i[each map collect select reject find_all
|
|
20
|
-
filter filter_map flat_map each_with_index
|
|
21
|
-
each_with_object reduce inject sum].freeze
|
|
12
|
+
ITERATION_METHODS = %i[each map collect select reject find_all filter filter_map flat_map
|
|
13
|
+
each_with_index each_with_object reduce inject sum].freeze
|
|
22
14
|
|
|
23
15
|
def self.detector_name
|
|
24
16
|
:missing_counter_cache
|
|
@@ -51,68 +43,45 @@ module EagerEye
|
|
|
51
43
|
|
|
52
44
|
def count_on_association?(node)
|
|
53
45
|
return false unless node.type == :send
|
|
54
|
-
|
|
55
|
-
method_name = node.children[1]
|
|
56
|
-
return false unless COUNT_METHODS.include?(method_name)
|
|
46
|
+
return false unless COUNT_METHODS.include?(node.children[1])
|
|
57
47
|
|
|
58
48
|
receiver = node.children[0]
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
likely_association_receiver?(receiver)
|
|
49
|
+
receiver && likely_association_receiver?(receiver)
|
|
62
50
|
end
|
|
63
51
|
|
|
64
52
|
def likely_association_receiver?(node)
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
method_name = node.children[1]
|
|
68
|
-
PLURAL_ASSOCIATIONS.include?(method_name.to_s)
|
|
53
|
+
node.type == :send && PLURAL_ASSOCIATIONS.include?(node.children[1].to_s)
|
|
69
54
|
end
|
|
70
55
|
|
|
71
56
|
def extract_association_name(node)
|
|
72
57
|
receiver = node.children[0]
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
receiver.children[1].to_s
|
|
58
|
+
receiver.children[1].to_s if receiver&.type == :send
|
|
76
59
|
end
|
|
77
60
|
|
|
78
|
-
# Check if the node is inside an iteration block
|
|
79
61
|
def inside_iteration?(node)
|
|
80
62
|
parent = node
|
|
81
|
-
while (parent =
|
|
63
|
+
while (parent = @parent_map[parent])
|
|
82
64
|
return true if iteration_block?(parent)
|
|
83
65
|
end
|
|
84
66
|
false
|
|
85
67
|
end
|
|
86
68
|
|
|
87
|
-
def find_parent(node)
|
|
88
|
-
@parent_map ||= {}
|
|
89
|
-
@parent_map[node]
|
|
90
|
-
end
|
|
91
|
-
|
|
92
|
-
# Override traverse_ast to build parent map
|
|
93
69
|
def traverse_ast(node, &block)
|
|
94
70
|
return unless node.is_a?(Parser::AST::Node)
|
|
95
71
|
|
|
96
72
|
@parent_map ||= {}
|
|
97
|
-
|
|
98
73
|
yield node
|
|
99
|
-
|
|
100
74
|
node.children.each do |child|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
75
|
+
next unless child.is_a?(Parser::AST::Node)
|
|
76
|
+
|
|
77
|
+
@parent_map[child] = node
|
|
78
|
+
traverse_ast(child, &block)
|
|
105
79
|
end
|
|
106
80
|
end
|
|
107
81
|
|
|
108
82
|
def iteration_block?(node)
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
send_node = node.children[0]
|
|
112
|
-
return false unless send_node&.type == :send
|
|
113
|
-
|
|
114
|
-
method_name = send_node.children[1]
|
|
115
|
-
ITERATION_METHODS.include?(method_name)
|
|
83
|
+
node.type == :block && node.children[0]&.type == :send &&
|
|
84
|
+
ITERATION_METHODS.include?(node.children[0].children[1])
|
|
116
85
|
end
|
|
117
86
|
end
|
|
118
87
|
end
|
|
@@ -49,100 +49,67 @@ module EagerEye
|
|
|
49
49
|
add_issue(node) if regular_pluck?(node)
|
|
50
50
|
end
|
|
51
51
|
|
|
52
|
-
def local_variable_assignment?(node)
|
|
53
|
-
node.type == :lvasgn
|
|
54
|
-
end
|
|
52
|
+
def local_variable_assignment?(node) = node.type == :lvasgn
|
|
55
53
|
|
|
56
|
-
def where_call?(node)
|
|
57
|
-
node.type == :send && node.children[1] == :where
|
|
58
|
-
end
|
|
54
|
+
def where_call?(node) = node.type == :send && node.children[1] == :where
|
|
59
55
|
|
|
60
56
|
def pluck_call?(node)
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
method = node.children[1]
|
|
64
|
-
%i[pluck ids].include?(method)
|
|
57
|
+
node.is_a?(Parser::AST::Node) && node.type == :send && %i[pluck ids].include?(node.children[1])
|
|
65
58
|
end
|
|
66
59
|
|
|
67
60
|
def all_pluck_call?(node)
|
|
68
61
|
return false unless pluck_call?(node)
|
|
69
62
|
|
|
70
63
|
receiver = node.children[0]
|
|
71
|
-
receiver.is_a?(Parser::AST::Node) && receiver.type == :send &&
|
|
72
|
-
receiver.children[1] == :all
|
|
64
|
+
receiver.is_a?(Parser::AST::Node) && receiver.type == :send && receiver.children[1] == :all
|
|
73
65
|
end
|
|
74
66
|
|
|
75
67
|
def map_id_call?(node)
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
block_map?(node) || send_map?(node)
|
|
68
|
+
node.is_a?(Parser::AST::Node) && (block_map?(node) || send_map?(node))
|
|
79
69
|
end
|
|
80
70
|
|
|
81
71
|
def block_map?(node)
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
send_node = node.children[0]
|
|
85
|
-
send_node&.type == :send && %i[map collect].include?(send_node.children[1])
|
|
72
|
+
node.type == :block && node.children[0]&.type == :send &&
|
|
73
|
+
%i[map collect].include?(node.children[0].children[1])
|
|
86
74
|
end
|
|
87
75
|
|
|
88
76
|
def send_map?(node)
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
method = node.children[1]
|
|
92
|
-
%i[map collect].include?(method) &&
|
|
77
|
+
node.type == :send && %i[map collect].include?(node.children[1]) &&
|
|
93
78
|
node.children[2..].any? { |arg| symbol_to_proc_id?(arg) }
|
|
94
79
|
end
|
|
95
80
|
|
|
96
81
|
def symbol_to_proc_id?(node)
|
|
97
82
|
return false unless node.is_a?(Parser::AST::Node) && node.type == :block_pass
|
|
98
83
|
|
|
99
|
-
sym
|
|
100
|
-
sym&.type == :sym && %i[id to_i].include?(sym.children[0])
|
|
84
|
+
node.children[0]&.type == :sym && %i[id to_i].include?(node.children[0].children[0])
|
|
101
85
|
end
|
|
102
86
|
|
|
103
87
|
def regular_pluck?(node)
|
|
104
|
-
|
|
105
|
-
where_args.any? { |arg| pluck_var_in_hash?(arg) }
|
|
88
|
+
node.children[2..].any? { |arg| pluck_var_in_hash?(arg) }
|
|
106
89
|
end
|
|
107
90
|
|
|
108
91
|
def critical_pluck?(node)
|
|
109
|
-
|
|
110
|
-
where_args.any? { |arg| critical_pluck_in_hash?(arg) }
|
|
92
|
+
node.children[2..].any? { |arg| critical_pluck_in_hash?(arg) }
|
|
111
93
|
end
|
|
112
94
|
|
|
113
95
|
def pluck_var_in_hash?(node)
|
|
114
96
|
return false unless node.is_a?(Parser::AST::Node) && node.type == :hash
|
|
115
97
|
|
|
116
|
-
node.children.any?
|
|
117
|
-
next false unless pair.type == :pair
|
|
118
|
-
|
|
119
|
-
pluck_value?(pair.children[1])
|
|
120
|
-
end
|
|
98
|
+
node.children.any? { |pair| pair.type == :pair && pluck_value?(pair.children[1]) }
|
|
121
99
|
end
|
|
122
100
|
|
|
123
101
|
def critical_pluck_in_hash?(node)
|
|
124
102
|
return false unless node.is_a?(Parser::AST::Node) && node.type == :hash
|
|
125
103
|
|
|
126
|
-
node.children.any?
|
|
127
|
-
next false unless pair.type == :pair
|
|
128
|
-
|
|
129
|
-
critical_value?(pair.children[1])
|
|
130
|
-
end
|
|
104
|
+
node.children.any? { |pair| pair.type == :pair && critical_value?(pair.children[1]) }
|
|
131
105
|
end
|
|
132
106
|
|
|
133
107
|
def pluck_value?(value)
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
var_name = value.children[0]
|
|
137
|
-
@pluck_variables.key?(var_name) || @map_id_variables.key?(var_name)
|
|
108
|
+
value.type == :lvar && (@pluck_variables.key?(value.children[0]) || @map_id_variables.key?(value.children[0]))
|
|
138
109
|
end
|
|
139
110
|
|
|
140
111
|
def critical_value?(value)
|
|
141
|
-
|
|
142
|
-
@critical_pluck_variables.key?(value.children[0])
|
|
143
|
-
else
|
|
144
|
-
all_pluck_call?(value)
|
|
145
|
-
end
|
|
112
|
+
value.type == :lvar ? @critical_pluck_variables.key?(value.children[0]) : all_pluck_call?(value)
|
|
146
113
|
end
|
|
147
114
|
|
|
148
115
|
def add_issue(node)
|
|
@@ -3,26 +3,10 @@
|
|
|
3
3
|
module EagerEye
|
|
4
4
|
module Detectors
|
|
5
5
|
class SerializerNesting < Base
|
|
6
|
-
|
|
7
|
-
SERIALIZER_PATTERNS = [
|
|
8
|
-
"ActiveModel::Serializer",
|
|
9
|
-
"ActiveModelSerializers::Model",
|
|
10
|
-
"Blueprinter::Base",
|
|
11
|
-
"Alba::Resource"
|
|
12
|
-
].freeze
|
|
13
|
-
|
|
14
|
-
# Method names that define attributes in serializers
|
|
6
|
+
SERIALIZER_PATTERNS = %w[ActiveModel::Serializer ActiveModelSerializers::Model Blueprinter::Base Alba::Resource].freeze
|
|
15
7
|
ATTRIBUTE_METHODS = %i[attribute field attributes].freeze
|
|
16
|
-
|
|
17
|
-
# Object reference names in serializers
|
|
18
8
|
OBJECT_REFS = %i[object record resource].freeze
|
|
19
|
-
|
|
20
|
-
# Common association names (same as LoopAssociation)
|
|
21
|
-
ASSOCIATION_NAMES = %w[
|
|
22
|
-
author user owner creator admin member customer client
|
|
23
|
-
post article comment category tag parent company organization
|
|
24
|
-
project task item order product account profile setting
|
|
25
|
-
image avatar photo attachment document
|
|
9
|
+
HAS_MANY_ASSOCIATIONS = %w[
|
|
26
10
|
authors users owners creators admins members customers clients
|
|
27
11
|
posts articles comments categories tags children companies organizations
|
|
28
12
|
projects tasks items orders products accounts profiles settings
|
|
@@ -52,21 +36,16 @@ module EagerEye
|
|
|
52
36
|
def serializer_class?(node)
|
|
53
37
|
return false unless node.type == :class
|
|
54
38
|
|
|
55
|
-
# Check class name ends with Serializer, Blueprint, or Resource
|
|
56
39
|
class_name = extract_class_name(node)
|
|
57
40
|
return false unless class_name
|
|
58
41
|
|
|
59
42
|
class_name.end_with?("Serializer", "Blueprint", "Resource") ||
|
|
60
|
-
inherits_from_serializer?(node) ||
|
|
61
|
-
includes_serializer_module?(node)
|
|
43
|
+
inherits_from_serializer?(node) || includes_serializer_module?(node)
|
|
62
44
|
end
|
|
63
45
|
|
|
64
46
|
def extract_class_name(class_node)
|
|
65
47
|
name_node = class_node.children[0]
|
|
66
|
-
|
|
67
|
-
return nil unless name_node.type == :const
|
|
68
|
-
|
|
69
|
-
name_node.children[1].to_s
|
|
48
|
+
name_node.children[1].to_s if name_node&.type == :const
|
|
70
49
|
end
|
|
71
50
|
|
|
72
51
|
def inherits_from_serializer?(class_node)
|
|
@@ -74,7 +53,7 @@ module EagerEye
|
|
|
74
53
|
return false unless parent_node
|
|
75
54
|
|
|
76
55
|
parent_name = const_to_string(parent_node)
|
|
77
|
-
SERIALIZER_PATTERNS.any? { |
|
|
56
|
+
SERIALIZER_PATTERNS.any? { |p| parent_name&.include?(p.split("::").last) }
|
|
78
57
|
end
|
|
79
58
|
|
|
80
59
|
def includes_serializer_module?(class_node)
|
|
@@ -82,20 +61,14 @@ module EagerEye
|
|
|
82
61
|
return false unless body
|
|
83
62
|
|
|
84
63
|
traverse_ast(body) do |node|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
method = node.children[1]
|
|
88
|
-
return true if method == :include && alba_resource?(node)
|
|
64
|
+
return true if node.type == :send && node.children[1] == :include && alba_resource?(node)
|
|
89
65
|
end
|
|
90
|
-
|
|
91
66
|
false
|
|
92
67
|
end
|
|
93
68
|
|
|
94
69
|
def alba_resource?(include_node)
|
|
95
70
|
arg = include_node.children[2]
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
const_to_string(arg)&.include?("Alba")
|
|
71
|
+
arg && const_to_string(arg)&.include?("Alba")
|
|
99
72
|
end
|
|
100
73
|
|
|
101
74
|
def const_to_string(node)
|
|
@@ -103,12 +76,10 @@ module EagerEye
|
|
|
103
76
|
|
|
104
77
|
parts = []
|
|
105
78
|
current = node
|
|
106
|
-
|
|
107
79
|
while current&.type == :const
|
|
108
80
|
parts.unshift(current.children[1].to_s)
|
|
109
81
|
current = current.children[0]
|
|
110
82
|
end
|
|
111
|
-
|
|
112
83
|
parts.join("::")
|
|
113
84
|
end
|
|
114
85
|
|
|
@@ -117,34 +88,24 @@ module EagerEye
|
|
|
117
88
|
return unless body
|
|
118
89
|
|
|
119
90
|
traverse_ast(body) do |node|
|
|
120
|
-
next unless attribute_block?(node)
|
|
121
|
-
|
|
122
|
-
block_body = node.children[2]
|
|
123
|
-
next unless block_body
|
|
91
|
+
next unless attribute_block?(node) && node.children[2]
|
|
124
92
|
|
|
125
|
-
find_association_in_block(
|
|
93
|
+
find_association_in_block(node.children[2], file_path, issues)
|
|
126
94
|
end
|
|
127
95
|
end
|
|
128
96
|
|
|
129
97
|
def attribute_block?(node)
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
send_node = node.children[0]
|
|
133
|
-
return false unless send_node&.type == :send
|
|
134
|
-
|
|
135
|
-
method_name = send_node.children[1]
|
|
136
|
-
ATTRIBUTE_METHODS.include?(method_name)
|
|
98
|
+
node.type == :block && node.children[0]&.type == :send &&
|
|
99
|
+
ATTRIBUTE_METHODS.include?(node.children[0].children[1])
|
|
137
100
|
end
|
|
138
101
|
|
|
139
|
-
def find_association_in_block(block_body,
|
|
102
|
+
def find_association_in_block(block_body, file_path, issues)
|
|
140
103
|
traverse_ast(block_body) do |node|
|
|
141
104
|
next unless node.type == :send
|
|
142
105
|
|
|
143
106
|
receiver = node.children[0]
|
|
144
107
|
method_name = node.children[1]
|
|
145
|
-
|
|
146
|
-
next unless object_reference?(receiver)
|
|
147
|
-
next unless likely_association?(method_name)
|
|
108
|
+
next unless object_reference?(receiver) && likely_association?(method_name)
|
|
148
109
|
|
|
149
110
|
issues << create_issue(
|
|
150
111
|
file_path: file_path,
|
|
@@ -159,33 +120,22 @@ module EagerEye
|
|
|
159
120
|
return false unless node
|
|
160
121
|
|
|
161
122
|
case node.type
|
|
162
|
-
when :send
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
method = node.children[1]
|
|
166
|
-
|
|
167
|
-
receiver.nil? && OBJECT_REFS.include?(method)
|
|
168
|
-
when :lvar
|
|
169
|
-
# Block variable like |post|
|
|
170
|
-
true
|
|
171
|
-
else
|
|
172
|
-
false
|
|
123
|
+
when :send then node.children[0].nil? && OBJECT_REFS.include?(node.children[1])
|
|
124
|
+
when :lvar then true
|
|
125
|
+
else false
|
|
173
126
|
end
|
|
174
127
|
end
|
|
175
128
|
|
|
176
129
|
def receiver_name(node)
|
|
177
130
|
case node.type
|
|
178
|
-
when :send
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
node.children[0].to_s
|
|
182
|
-
else
|
|
183
|
-
"object"
|
|
131
|
+
when :send then node.children[1].to_s
|
|
132
|
+
when :lvar then node.children[0].to_s
|
|
133
|
+
else "object"
|
|
184
134
|
end
|
|
185
135
|
end
|
|
186
136
|
|
|
187
137
|
def likely_association?(method_name)
|
|
188
|
-
|
|
138
|
+
HAS_MANY_ASSOCIATIONS.include?(method_name.to_s)
|
|
189
139
|
end
|
|
190
140
|
end
|
|
191
141
|
end
|
|
@@ -3,27 +3,20 @@
|
|
|
3
3
|
module EagerEye
|
|
4
4
|
module Fixers
|
|
5
5
|
class PluckToSelect < Base
|
|
6
|
-
# This fixer only works for single-line pluck + where patterns
|
|
7
|
-
# Two-line patterns are too complex to fix automatically
|
|
8
|
-
|
|
9
6
|
def fixable?
|
|
10
|
-
issue.detector == :pluck_to_array &&
|
|
11
|
-
single_line_pattern?
|
|
7
|
+
issue.detector == :pluck_to_array && single_line_pattern?
|
|
12
8
|
end
|
|
13
9
|
|
|
14
10
|
protected
|
|
15
11
|
|
|
16
12
|
def fixed_content
|
|
17
|
-
# Model.where(col: OtherModel.pluck(:id)) -> Model.where(col: OtherModel.select(:id))
|
|
18
13
|
line_content.gsub(/\.pluck\((:\w+)\)/, '.select(\1)')
|
|
19
14
|
end
|
|
20
15
|
|
|
21
16
|
private
|
|
22
17
|
|
|
23
18
|
def single_line_pattern?
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
line_content.include?(".pluck(") && line_content.include?(".where(")
|
|
19
|
+
line_content&.include?(".pluck(") && line_content.include?(".where(")
|
|
27
20
|
end
|
|
28
21
|
end
|
|
29
22
|
end
|
data/lib/eager_eye/issue.rb
CHANGED
|
@@ -40,20 +40,13 @@ module EagerEye
|
|
|
40
40
|
end
|
|
41
41
|
|
|
42
42
|
def ==(other)
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
detector == other.detector &&
|
|
46
|
-
file_path == other.file_path &&
|
|
47
|
-
line_number == other.line_number &&
|
|
48
|
-
message == other.message &&
|
|
49
|
-
severity == other.severity &&
|
|
50
|
-
suggestion == other.suggestion
|
|
43
|
+
other.is_a?(Issue) && to_h == other.to_h
|
|
51
44
|
end
|
|
52
45
|
|
|
53
46
|
alias eql? ==
|
|
54
47
|
|
|
55
48
|
def hash
|
|
56
|
-
|
|
49
|
+
to_h.hash
|
|
57
50
|
end
|
|
58
51
|
|
|
59
52
|
private
|
data/lib/eager_eye/railtie.rb
CHANGED
|
@@ -10,32 +10,20 @@ module EagerEye
|
|
|
10
10
|
namespace :eager_eye do
|
|
11
11
|
desc "Analyze Rails application for N+1 query issues"
|
|
12
12
|
task analyze: :environment do
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
load_config_file
|
|
16
|
-
|
|
17
|
-
analyzer = EagerEye::Analyzer.new
|
|
18
|
-
issues = analyzer.run
|
|
19
|
-
|
|
20
|
-
reporter = EagerEye::Reporters::Console.new(issues)
|
|
21
|
-
puts reporter.report
|
|
22
|
-
|
|
23
|
-
exit 1 if issues.any? && EagerEye.configuration.fail_on_issues
|
|
13
|
+
puts run_analysis(EagerEye::Reporters::Console)
|
|
24
14
|
end
|
|
25
15
|
|
|
26
16
|
desc "Analyze and output results as JSON"
|
|
27
17
|
task json: :environment do
|
|
28
|
-
|
|
18
|
+
puts run_analysis(EagerEye::Reporters::Json, pretty: true)
|
|
19
|
+
end
|
|
29
20
|
|
|
21
|
+
def run_analysis(reporter_class, **opts)
|
|
22
|
+
require "eager_eye"
|
|
30
23
|
load_config_file
|
|
31
|
-
|
|
32
|
-
analyzer = EagerEye::Analyzer.new
|
|
33
|
-
issues = analyzer.run
|
|
34
|
-
|
|
35
|
-
reporter = EagerEye::Reporters::Json.new(issues, pretty: true)
|
|
36
|
-
puts reporter.report
|
|
37
|
-
|
|
24
|
+
issues = EagerEye::Analyzer.new.run
|
|
38
25
|
exit 1 if issues.any? && EagerEye.configuration.fail_on_issues
|
|
26
|
+
reporter_class.new(issues, **opts).report
|
|
39
27
|
end
|
|
40
28
|
|
|
41
29
|
def load_config_file
|
|
@@ -55,7 +43,6 @@ module EagerEye
|
|
|
55
43
|
end
|
|
56
44
|
end
|
|
57
45
|
|
|
58
|
-
# Generate initializer for configuration
|
|
59
46
|
generators do
|
|
60
47
|
require_relative "generators/install_generator"
|
|
61
48
|
end
|
|
@@ -48,15 +48,7 @@ module EagerEye
|
|
|
48
48
|
end
|
|
49
49
|
|
|
50
50
|
def file_section(file_path, file_issues)
|
|
51
|
-
|
|
52
|
-
lines << colorize(file_path, :cyan)
|
|
53
|
-
|
|
54
|
-
file_issues.each do |issue|
|
|
55
|
-
lines << format_issue(issue)
|
|
56
|
-
end
|
|
57
|
-
|
|
58
|
-
lines << ""
|
|
59
|
-
lines.join("\n")
|
|
51
|
+
[colorize(file_path, :cyan), *file_issues.map { |i| format_issue(i) }, ""].join("\n")
|
|
60
52
|
end
|
|
61
53
|
|
|
62
54
|
def format_issue(issue)
|
|
@@ -81,15 +73,12 @@ module EagerEye
|
|
|
81
73
|
end
|
|
82
74
|
|
|
83
75
|
def summary
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
"(#{errors} error#{"s" unless errors == 1}, " \
|
|
91
|
-
"#{warnings} warning#{"s" unless warnings == 1}, " \
|
|
92
|
-
"#{infos} info)"
|
|
76
|
+
t = issues.size
|
|
77
|
+
e = error_count
|
|
78
|
+
w = warning_count
|
|
79
|
+
i = info_count
|
|
80
|
+
"Total: #{t} issue#{"s" unless t == 1} " \
|
|
81
|
+
"(#{e} error#{"s" unless e == 1}, #{w} warning#{"s" unless w == 1}, #{i} info)"
|
|
93
82
|
end
|
|
94
83
|
|
|
95
84
|
def colorize(text, color)
|
|
@@ -19,8 +19,7 @@ module EagerEye
|
|
|
19
19
|
def matches?(path)
|
|
20
20
|
@path = path
|
|
21
21
|
configure_eager_eye
|
|
22
|
-
|
|
23
|
-
@issues = analyzer.run
|
|
22
|
+
@issues = EagerEye::Analyzer.new(paths: [@path]).run
|
|
24
23
|
@issues.count <= @max_issues
|
|
25
24
|
end
|
|
26
25
|
|
|
@@ -61,10 +60,6 @@ module EagerEye
|
|
|
61
60
|
config.fail_on_issues = false
|
|
62
61
|
end
|
|
63
62
|
end
|
|
64
|
-
|
|
65
|
-
def build_analyzer
|
|
66
|
-
EagerEye::Analyzer.new(paths: [@path])
|
|
67
|
-
end
|
|
68
63
|
end
|
|
69
64
|
end
|
|
70
65
|
end
|
data/lib/eager_eye/version.rb
CHANGED
data/lib/eager_eye.rb
CHANGED
data/sig/eager_eye.rbs
CHANGED