eager_eye 0.2.0 → 0.2.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4adfe62f9c9082d1c0f1e9b149af5f2cc09db5a455af6c0262687a5770b2c50b
4
- data.tar.gz: 5284a262cf22fc9e7191dafa0a45383b3f073565313e02d202fb902bc250e7bb
3
+ metadata.gz: 2c1781e66205a83557a9751b64f19c44d70f90efbd04423d63e847d4e4a06c6e
4
+ data.tar.gz: eac8bb2e4a8b86017804b2c9662074ece08346a5303b5760bdba6dc7d939b3d9
5
5
  SHA512:
6
- metadata.gz: e40d5802cd52757817d3c6f071abb7203d05ca5b127a72ee5534066b59b4addb2fdec1a592f703864710eec81fa571ef774ada0cd2381995b3819b711366d939
7
- data.tar.gz: 50d3bcfa2029032200cae2b4e05ba5ef319f1a981acfc1b8b2969e0dcd977d849d01c2edab51f42808d0bdda538a63be280ad2063f5927907b64e579185c223e
6
+ metadata.gz: 9a9fec60ec826824ef8b331f95f33918265f51ea027b43f60852656a0b1420622b9405a78f0fdc4ba3e32c9a30159deb5104fe7432b35fdf9bdea8e5325aa120
7
+ data.tar.gz: fe4032176ada8b10c84ee56c48a8ee60aadbf7c6547e98e0f36def0226a119a7383e6258a060a197b4b6276667fd65f30707dbaeda155ff9410551b2111167ed
@@ -0,0 +1,145 @@
1
+ # frozen_string_literal: true
2
+
3
+ module EagerEye
4
+ module Detectors
5
+ class CustomMethodQuery < Base
6
+ QUERY_METHODS = %i[
7
+ where
8
+ find_by
9
+ find_by!
10
+ exists?
11
+ find
12
+ first
13
+ last
14
+ take
15
+ pluck
16
+ ids
17
+ count
18
+ sum
19
+ average
20
+ minimum
21
+ maximum
22
+ ].freeze
23
+
24
+ ITERATION_METHODS = %i[each map select find_all reject collect detect find_index flat_map].freeze
25
+
26
+ def self.detector_name
27
+ :custom_method_query
28
+ end
29
+
30
+ def detect(ast, file_path)
31
+ return [] unless ast
32
+
33
+ @issues = []
34
+ @file_path = file_path
35
+
36
+ find_iteration_blocks(ast) do |block_body, block_var|
37
+ check_block_for_query_methods(block_body, block_var)
38
+ end
39
+
40
+ @issues
41
+ end
42
+
43
+ private
44
+
45
+ def find_iteration_blocks(node, &block)
46
+ return unless node.is_a?(Parser::AST::Node)
47
+
48
+ if iteration_block?(node)
49
+ block_var = extract_block_variable(node)
50
+ block_body = extract_block_body(node)
51
+ yield(block_body, block_var) if block_var && block_body
52
+ end
53
+
54
+ node.children.each do |child|
55
+ find_iteration_blocks(child, &block)
56
+ end
57
+ end
58
+
59
+ def iteration_block?(node)
60
+ return false unless node.type == :block
61
+
62
+ send_node = node.children[0]
63
+ return false unless send_node&.type == :send
64
+
65
+ method_name = send_node.children[1]
66
+ ITERATION_METHODS.include?(method_name)
67
+ end
68
+
69
+ def check_block_for_query_methods(node, block_var)
70
+ return unless node.is_a?(Parser::AST::Node)
71
+
72
+ add_issue(node) if query_chain_on_association?(node, block_var)
73
+
74
+ node.children.each do |child|
75
+ check_block_for_query_methods(child, block_var)
76
+ end
77
+ end
78
+
79
+ def query_chain_on_association?(node, block_var)
80
+ return false unless node.type == :send
81
+
82
+ method_name = node.children[1]
83
+ return false unless QUERY_METHODS.include?(method_name)
84
+
85
+ receiver = node.children[0]
86
+ receiver_chain_starts_with?(receiver, block_var)
87
+ end
88
+
89
+ def receiver_chain_starts_with?(node, block_var)
90
+ return false unless node.is_a?(Parser::AST::Node)
91
+
92
+ case node.type
93
+ when :lvar
94
+ node.children[0] == block_var
95
+ when :send
96
+ receiver_chain_starts_with?(node.children[0], block_var)
97
+ else
98
+ false
99
+ end
100
+ end
101
+
102
+ def extract_block_variable(block_node)
103
+ args_node = block_node.children[1]
104
+ return nil unless args_node&.type == :args
105
+
106
+ first_arg = args_node.children[0]
107
+ return nil unless first_arg&.type == :arg
108
+
109
+ first_arg.children[0]
110
+ end
111
+
112
+ def extract_block_body(block_node)
113
+ block_node.children[2]
114
+ end
115
+
116
+ def add_issue(node)
117
+ method_name = node.children[1]
118
+ association_chain = reconstruct_chain(node.children[0])
119
+
120
+ @issues << create_issue(
121
+ file_path: @file_path,
122
+ line_number: node.loc.line,
123
+ message: "Query method `.#{method_name}` called on `#{association_chain}` inside iteration",
124
+ severity: :warning,
125
+ suggestion: "This query executes on each iteration. Consider preloading data or restructuring the query."
126
+ )
127
+ end
128
+
129
+ def reconstruct_chain(node)
130
+ return "" unless node.is_a?(Parser::AST::Node)
131
+
132
+ case node.type
133
+ when :lvar
134
+ node.children[0].to_s
135
+ when :send
136
+ receiver_str = reconstruct_chain(node.children[0])
137
+ method = node.children[1]
138
+ receiver_str.empty? ? method.to_s : "#{receiver_str}.#{method}"
139
+ else
140
+ ""
141
+ end
142
+ end
143
+ end
144
+ end
145
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module EagerEye
4
- VERSION = "0.2.0"
4
+ VERSION = "0.2.1"
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.0
4
+ version: 0.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - hamzagedikkaya
@@ -61,6 +61,7 @@ files:
61
61
  - lib/eager_eye/cli.rb
62
62
  - lib/eager_eye/configuration.rb
63
63
  - lib/eager_eye/detectors/base.rb
64
+ - lib/eager_eye/detectors/custom_method_query.rb
64
65
  - lib/eager_eye/detectors/loop_association.rb
65
66
  - lib/eager_eye/detectors/missing_counter_cache.rb
66
67
  - lib/eager_eye/detectors/serializer_nesting.rb