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: 2c1781e66205a83557a9751b64f19c44d70f90efbd04423d63e847d4e4a06c6e
4
- data.tar.gz: eac8bb2e4a8b86017804b2c9662074ece08346a5303b5760bdba6dc7d939b3d9
3
+ metadata.gz: 251f5ec97df15b5f85cbea7b1be3b7c95961f6ed133f4a69b721d76ebd7ad40e
4
+ data.tar.gz: ce06033abc65e06113af34789bf17a97badcfe8076675a45bf5ee59f5e19af79
5
5
  SHA512:
6
- metadata.gz: 9a9fec60ec826824ef8b331f95f33918265f51ea027b43f60852656a0b1420622b9405a78f0fdc4ba3e32c9a30159deb5104fe7432b35fdf9bdea8e5325aa120
7
- data.tar.gz: fe4032176ada8b10c84ee56c48a8ee60aadbf7c6547e98e0f36def0226a119a7383e6258a060a197b4b6276667fd65f30707dbaeda155ff9410551b2111167ed
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
- check_block_for_query_methods(block_body, block_var)
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
- yield(block_body, block_var) if block_var && block_body
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
- find_association_calls(block_body, block_var, file_path, issues)
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 find_association_calls(node, block_var, file_path, issues)
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)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module EagerEye
4
- VERSION = "0.2.1"
4
+ VERSION = "0.2.3"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: eager_eye
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.1
4
+ version: 0.2.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - hamzagedikkaya