eager_eye 0.2.1 → 0.2.3
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
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 251f5ec97df15b5f85cbea7b1be3b7c95961f6ed133f4a69b721d76ebd7ad40e
|
|
4
|
+
data.tar.gz: ce06033abc65e06113af34789bf17a97badcfe8076675a45bf5ee59f5e19af79
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 9f24578b9ec1a121b1698e3d53d78ec570b12b3f5092289ca42adc421c89cd1a94566e5de6ff45309803c0be2b8bafc76b1ea3e9f78599da32aef87503c51c38
|
|
7
|
+
data.tar.gz: 3f9dd3a3a3d4116840d8de2392e06afa78486ae049505f8706a7e5c9303d25e7efcf0ba7522b256bb9e504c1d8b709d93e06e92b1d0391cfd0e3bafdb7e00536
|
|
@@ -21,6 +21,9 @@ module EagerEye
|
|
|
21
21
|
maximum
|
|
22
22
|
].freeze
|
|
23
23
|
|
|
24
|
+
# Array-only methods that should not be flagged when collection is clearly an array
|
|
25
|
+
ARRAY_METHODS = %i[first last take].freeze
|
|
26
|
+
|
|
24
27
|
ITERATION_METHODS = %i[each map select find_all reject collect detect find_index flat_map].freeze
|
|
25
28
|
|
|
26
29
|
def self.detector_name
|
|
@@ -33,8 +36,9 @@ module EagerEye
|
|
|
33
36
|
@issues = []
|
|
34
37
|
@file_path = file_path
|
|
35
38
|
|
|
36
|
-
find_iteration_blocks(ast) do |block_body, block_var|
|
|
37
|
-
|
|
39
|
+
find_iteration_blocks(ast) do |block_body, block_var, collection|
|
|
40
|
+
is_array_collection = collection_is_array?(collection)
|
|
41
|
+
check_block_for_query_methods(block_body, block_var, is_array_collection)
|
|
38
42
|
end
|
|
39
43
|
|
|
40
44
|
@issues
|
|
@@ -48,7 +52,8 @@ module EagerEye
|
|
|
48
52
|
if iteration_block?(node)
|
|
49
53
|
block_var = extract_block_variable(node)
|
|
50
54
|
block_body = extract_block_body(node)
|
|
51
|
-
|
|
55
|
+
collection = extract_collection(node)
|
|
56
|
+
yield(block_body, block_var, collection) if block_var && block_body
|
|
52
57
|
end
|
|
53
58
|
|
|
54
59
|
node.children.each do |child|
|
|
@@ -66,26 +71,40 @@ module EagerEye
|
|
|
66
71
|
ITERATION_METHODS.include?(method_name)
|
|
67
72
|
end
|
|
68
73
|
|
|
69
|
-
def check_block_for_query_methods(node, block_var)
|
|
74
|
+
def check_block_for_query_methods(node, block_var, is_array_collection = false) # rubocop:disable Style/OptionalBooleanParameter
|
|
70
75
|
return unless node.is_a?(Parser::AST::Node)
|
|
71
76
|
|
|
72
|
-
add_issue(node) if query_chain_on_association?(node, block_var)
|
|
77
|
+
add_issue(node) if query_chain_on_association?(node, block_var, is_array_collection)
|
|
73
78
|
|
|
74
79
|
node.children.each do |child|
|
|
75
|
-
check_block_for_query_methods(child, block_var)
|
|
80
|
+
check_block_for_query_methods(child, block_var, is_array_collection)
|
|
76
81
|
end
|
|
77
82
|
end
|
|
78
83
|
|
|
79
|
-
def query_chain_on_association?(node, block_var)
|
|
84
|
+
def query_chain_on_association?(node, block_var, is_array_collection = false) # rubocop:disable Style/OptionalBooleanParameter
|
|
80
85
|
return false unless node.type == :send
|
|
81
86
|
|
|
82
87
|
method_name = node.children[1]
|
|
83
88
|
return false unless QUERY_METHODS.include?(method_name)
|
|
84
89
|
|
|
90
|
+
# Skip array-only methods when collection is clearly an array (.map result)
|
|
91
|
+
# AND the receiver is only the block variable (not chained)
|
|
92
|
+
if is_array_collection && ARRAY_METHODS.include?(method_name) &&
|
|
93
|
+
receiver_is_only_block_var?(node.children[0], block_var)
|
|
94
|
+
return false
|
|
95
|
+
end
|
|
96
|
+
|
|
85
97
|
receiver = node.children[0]
|
|
86
98
|
receiver_chain_starts_with?(receiver, block_var)
|
|
87
99
|
end
|
|
88
100
|
|
|
101
|
+
def receiver_is_only_block_var?(node, block_var)
|
|
102
|
+
# Returns true only if receiver is EXACTLY the block variable, not a chain
|
|
103
|
+
node.is_a?(Parser::AST::Node) &&
|
|
104
|
+
node.type == :lvar &&
|
|
105
|
+
node.children[0] == block_var
|
|
106
|
+
end
|
|
107
|
+
|
|
89
108
|
def receiver_chain_starts_with?(node, block_var)
|
|
90
109
|
return false unless node.is_a?(Parser::AST::Node)
|
|
91
110
|
|
|
@@ -113,6 +132,31 @@ module EagerEye
|
|
|
113
132
|
block_node.children[2]
|
|
114
133
|
end
|
|
115
134
|
|
|
135
|
+
def extract_collection(block_node)
|
|
136
|
+
# Extract the collection being iterated on
|
|
137
|
+
# For: collection.each { |item| ... }
|
|
138
|
+
# Returns: the send node representing the collection method call
|
|
139
|
+
block_node.children[0]
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
def collection_is_array?(collection_node)
|
|
143
|
+
return false unless collection_node.is_a?(Parser::AST::Node)
|
|
144
|
+
|
|
145
|
+
case collection_node.type
|
|
146
|
+
when :array
|
|
147
|
+
# Literal array: [1, 2, 3].each { |item| ... }
|
|
148
|
+
true
|
|
149
|
+
when :send
|
|
150
|
+
# Only consider these methods as definitely returning arrays when iterating
|
|
151
|
+
method_name = collection_node.children[1]
|
|
152
|
+
# map, select, collect, etc. on anything return arrays for iteration
|
|
153
|
+
%i[map select collect flat_map to_a uniq compact].include?(method_name)
|
|
154
|
+
else
|
|
155
|
+
# Block variable itself won't tell us if it's an array
|
|
156
|
+
false
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
|
|
116
160
|
def add_issue(node)
|
|
117
161
|
method_name = node.children[1]
|
|
118
162
|
association_chain = reconstruct_chain(node.children[0])
|
|
@@ -49,7 +49,11 @@ module EagerEye
|
|
|
49
49
|
block_body = node.children[2]
|
|
50
50
|
next unless block_body
|
|
51
51
|
|
|
52
|
-
|
|
52
|
+
# Check if the collection already has includes
|
|
53
|
+
collection_node = node.children[0]
|
|
54
|
+
included_associations = extract_included_associations(collection_node)
|
|
55
|
+
|
|
56
|
+
find_association_calls(block_body, block_var, file_path, issues, included_associations)
|
|
53
57
|
end
|
|
54
58
|
|
|
55
59
|
issues
|
|
@@ -78,7 +82,44 @@ module EagerEye
|
|
|
78
82
|
first_arg.children[0]
|
|
79
83
|
end
|
|
80
84
|
|
|
81
|
-
def
|
|
85
|
+
def extract_included_associations(collection_node)
|
|
86
|
+
included = Set.new
|
|
87
|
+
return included unless collection_node&.type == :send
|
|
88
|
+
|
|
89
|
+
# Traverse through chained method calls to find includes()
|
|
90
|
+
current = collection_node
|
|
91
|
+
while current&.type == :send
|
|
92
|
+
method_name = current.children[1]
|
|
93
|
+
extract_includes_from_method(current, included) if method_name == :includes
|
|
94
|
+
|
|
95
|
+
current = current.children[0]
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
included
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def extract_includes_from_method(method_node, included_set)
|
|
102
|
+
args = method_node.children[2..]
|
|
103
|
+
args&.each do |arg|
|
|
104
|
+
case arg&.type
|
|
105
|
+
when :sym
|
|
106
|
+
# includes(:product)
|
|
107
|
+
included_set.add(arg.children[0])
|
|
108
|
+
when :hash
|
|
109
|
+
# includes(product: :manufacturer)
|
|
110
|
+
extract_from_hash(arg, included_set)
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def extract_from_hash(hash_node, included_set)
|
|
116
|
+
hash_node.children.each do |pair|
|
|
117
|
+
key = pair.children[0]
|
|
118
|
+
included_set.add(key.children[0]) if key&.type == :sym
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def find_association_calls(node, block_var, file_path, issues, included_associations = Set.new)
|
|
82
123
|
reported_associations = Set.new
|
|
83
124
|
|
|
84
125
|
traverse_ast(node) do |child|
|
|
@@ -91,6 +132,9 @@ module EagerEye
|
|
|
91
132
|
next unless direct_call_on_block_var?(receiver, block_var)
|
|
92
133
|
next unless likely_association?(method_name)
|
|
93
134
|
|
|
135
|
+
# Skip if association is already included
|
|
136
|
+
next if included_associations.include?(method_name)
|
|
137
|
+
|
|
94
138
|
# Avoid duplicate reports for same association on same line
|
|
95
139
|
report_key = "#{child.loc.line}:#{method_name}"
|
|
96
140
|
next if reported_associations.include?(report_key)
|
data/lib/eager_eye/version.rb
CHANGED