eager_eye 1.0.5 → 1.0.7
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 +17 -0
- data/README.md +34 -15
- data/images/icon.png +0 -0
- data/lib/eager_eye/comment_parser.rb +2 -2
- data/lib/eager_eye/detectors/missing_counter_cache.rb +47 -1
- data/lib/eager_eye/version.rb +1 -1
- metadata +3 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: f19a4d72ee31d2ea100782d4fd9da6594db5f9ec78e9290723461441f58e264d
|
|
4
|
+
data.tar.gz: d8b347ecb77010db7ebff3db74610de1516c653991d7264321f302d2b3eaf0ea
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 6e676774f31b89d5a6948dda61c18e6c08a522f83c224d796a16dc954f3ae2fd6ef63e91bb1d4e9afe652ab4447538af5e521875bc2544833cabf429549582ff
|
|
7
|
+
data.tar.gz: a6bde55b11a3cb95f2550ddd5592b855e2ed6accd36abf3e1fb33f00ba34343291e0eab1a17253de95990c876a9119d15ef1b9e2548cc706e7dea0c6c446a510
|
data/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,23 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [1.0.7] - 2025-12-24
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
|
|
14
|
+
- Fixed `invalid byte sequence in US-ASCII` error when parsing files containing non-ASCII characters (Turkish, Chinese, etc.)
|
|
15
|
+
- Now properly encodes source code to UTF-8 with replacement for invalid/undefined characters
|
|
16
|
+
- Fixes crash when analyzing files with special characters in comments or strings
|
|
17
|
+
|
|
18
|
+
## [1.0.6] - 2025-12-22
|
|
19
|
+
|
|
20
|
+
### Fixed
|
|
21
|
+
|
|
22
|
+
- Fixed false positive in `MissingCounterCache` detector for single `.count`/`.size`/`.length` calls
|
|
23
|
+
- Now only detects count calls **inside iterations** where N+1 queries actually occur
|
|
24
|
+
- Single calls like `post.comments.count` are no longer flagged (not N+1)
|
|
25
|
+
- Iteration patterns like `posts.each { |p| p.comments.count }` are correctly detected
|
|
26
|
+
|
|
10
27
|
## [1.0.5] - 2025-12-21
|
|
11
28
|
|
|
12
29
|
### Fixed
|
data/README.md
CHANGED
|
@@ -1,15 +1,27 @@
|
|
|
1
|
-
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="images/icon.png" alt="EagerEye Logo" width="140">
|
|
3
|
+
</p>
|
|
2
4
|
|
|
3
|
-
|
|
4
|
-
[](https://rubygems.org/gems/eager_eye)
|
|
5
|
-
[](https://github.com/hamzagedikkaya/eager_eye)
|
|
6
|
-
[](https://www.ruby-lang.org/)
|
|
7
|
-
[](https://opensource.org/licenses/MIT)
|
|
8
|
-
[](https://marketplace.visualstudio.com/items?itemName=hamzagedikkaya.eager-eye)
|
|
5
|
+
<h1 align="center">EagerEye</h1>
|
|
9
6
|
|
|
10
|
-
|
|
7
|
+
<p align="center">
|
|
8
|
+
<strong>Static analysis tool for detecting N+1 queries in Rails applications.</strong>
|
|
9
|
+
</p>
|
|
11
10
|
|
|
12
|
-
|
|
11
|
+
<p align="center">
|
|
12
|
+
<a href="https://github.com/hamzagedikkaya/eager_eye/actions/workflows/main.yml"><img src="https://github.com/hamzagedikkaya/eager_eye/actions/workflows/main.yml/badge.svg" alt="CI"></a>
|
|
13
|
+
<a href="https://rubygems.org/gems/eager_eye"><img src="https://img.shields.io/badge/gem-v1.0.7-red.svg" alt="Gem Version"></a>
|
|
14
|
+
<a href="https://github.com/hamzagedikkaya/eager_eye"><img src="https://img.shields.io/badge/coverage-95%25-brightgreen.svg" alt="Coverage"></a>
|
|
15
|
+
<a href="https://www.ruby-lang.org/"><img src="https://img.shields.io/badge/ruby-%3E%3D%203.1-ruby.svg" alt="Ruby"></a>
|
|
16
|
+
<a href="https://opensource.org/licenses/MIT"><img src="https://img.shields.io/badge/License-MIT-yellow.svg" alt="License: MIT"></a>
|
|
17
|
+
<a href="https://marketplace.visualstudio.com/items?itemName=hamzagedikkaya.eager-eye"><img src="https://img.shields.io/badge/VS%20Code-Extension-blue.svg" alt="VS Code Extension"></a>
|
|
18
|
+
</p>
|
|
19
|
+
|
|
20
|
+
<p align="center">
|
|
21
|
+
<em>Analyze your Ruby code without running it — find N+1 query issues before they hit production using AST parsing.</em>
|
|
22
|
+
</p>
|
|
23
|
+
|
|
24
|
+
---
|
|
13
25
|
|
|
14
26
|
## Why EagerEye?
|
|
15
27
|
|
|
@@ -150,19 +162,26 @@ Supports multiple serializer libraries:
|
|
|
150
162
|
|
|
151
163
|
### 3. Missing Counter Cache
|
|
152
164
|
|
|
153
|
-
Detects `.count
|
|
165
|
+
Detects `.count`, `.size`, or `.length` calls on associations **inside iterations** that could benefit from counter caches. Single calls outside loops are not flagged since they don't cause N+1 issues.
|
|
154
166
|
|
|
155
167
|
```ruby
|
|
156
|
-
# Bad - COUNT query
|
|
157
|
-
post
|
|
158
|
-
post.comments.
|
|
168
|
+
# Bad - COUNT query for each post in iteration
|
|
169
|
+
posts.each do |post|
|
|
170
|
+
post.comments.count # Detected: N+1 query!
|
|
171
|
+
post.likes.size # Detected: N+1 query!
|
|
172
|
+
end
|
|
159
173
|
|
|
160
|
-
#
|
|
174
|
+
# OK - Single count call (not in iteration, no N+1)
|
|
175
|
+
post.comments.count # Not flagged - single query is fine
|
|
176
|
+
|
|
177
|
+
# Good - Add counter cache for iteration use cases
|
|
161
178
|
# In Comment model:
|
|
162
179
|
belongs_to :post, counter_cache: true
|
|
163
180
|
|
|
164
181
|
# Then this is a simple column read:
|
|
165
|
-
post
|
|
182
|
+
posts.each do |post|
|
|
183
|
+
post.comments_count # No query - just reads the column
|
|
184
|
+
end
|
|
166
185
|
```
|
|
167
186
|
|
|
168
187
|
### 4. Custom Method Query (N+1 in query methods)
|
data/images/icon.png
ADDED
|
Binary file
|
|
@@ -10,8 +10,8 @@ module EagerEye
|
|
|
10
10
|
BLOCK_DISABLE_PATTERN = /eager_eye:disable\s+(.+?)(?:\s+--|$)/i
|
|
11
11
|
|
|
12
12
|
def initialize(source_code)
|
|
13
|
-
@source_code = source_code
|
|
14
|
-
@lines = source_code.lines
|
|
13
|
+
@source_code = source_code.encode("UTF-8", invalid: :replace, undef: :replace)
|
|
14
|
+
@lines = @source_code.lines
|
|
15
15
|
@disabled_ranges = Hash.new { |h, k| h[k] = [] }
|
|
16
16
|
@file_disabled = Set.new
|
|
17
17
|
@current_disabled = Set.new
|
|
@@ -15,6 +15,11 @@ module EagerEye
|
|
|
15
15
|
children replies responses answers questions
|
|
16
16
|
].freeze
|
|
17
17
|
|
|
18
|
+
# Iteration methods that indicate a loop context
|
|
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
|
|
22
|
+
|
|
18
23
|
def self.detector_name
|
|
19
24
|
:missing_counter_cache
|
|
20
25
|
end
|
|
@@ -26,6 +31,7 @@ module EagerEye
|
|
|
26
31
|
|
|
27
32
|
traverse_ast(ast) do |node|
|
|
28
33
|
next unless count_on_association?(node)
|
|
34
|
+
next unless inside_iteration?(node)
|
|
29
35
|
|
|
30
36
|
association_name = extract_association_name(node)
|
|
31
37
|
next unless association_name
|
|
@@ -33,7 +39,7 @@ module EagerEye
|
|
|
33
39
|
issues << create_issue(
|
|
34
40
|
file_path: file_path,
|
|
35
41
|
line_number: node.loc.line,
|
|
36
|
-
message: "`.#{node.children[1]}` called on `#{association_name}` may cause N+1 queries",
|
|
42
|
+
message: "`.#{node.children[1]}` called on `#{association_name}` inside iteration may cause N+1 queries",
|
|
37
43
|
suggestion: "Consider adding `counter_cache: true` to the belongs_to association"
|
|
38
44
|
)
|
|
39
45
|
end
|
|
@@ -68,6 +74,46 @@ module EagerEye
|
|
|
68
74
|
|
|
69
75
|
receiver.children[1].to_s
|
|
70
76
|
end
|
|
77
|
+
|
|
78
|
+
# Check if the node is inside an iteration block
|
|
79
|
+
def inside_iteration?(node)
|
|
80
|
+
parent = node
|
|
81
|
+
while (parent = find_parent(parent))
|
|
82
|
+
return true if iteration_block?(parent)
|
|
83
|
+
end
|
|
84
|
+
false
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def find_parent(node)
|
|
88
|
+
@parent_map ||= {}
|
|
89
|
+
@parent_map[node]
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# Override traverse_ast to build parent map
|
|
93
|
+
def traverse_ast(node, &block)
|
|
94
|
+
return unless node.is_a?(Parser::AST::Node)
|
|
95
|
+
|
|
96
|
+
@parent_map ||= {}
|
|
97
|
+
|
|
98
|
+
yield node
|
|
99
|
+
|
|
100
|
+
node.children.each do |child|
|
|
101
|
+
if child.is_a?(Parser::AST::Node)
|
|
102
|
+
@parent_map[child] = node
|
|
103
|
+
traverse_ast(child, &block)
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def iteration_block?(node)
|
|
109
|
+
return false unless node.type == :block
|
|
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)
|
|
116
|
+
end
|
|
71
117
|
end
|
|
72
118
|
end
|
|
73
119
|
end
|
data/lib/eager_eye/version.rb
CHANGED
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.0.
|
|
4
|
+
version: 1.0.7
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- hamzagedikkaya
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2025-12-
|
|
11
|
+
date: 2025-12-24 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: ast
|
|
@@ -58,6 +58,7 @@ files:
|
|
|
58
58
|
- SECURITY.md
|
|
59
59
|
- examples/github_action.yml
|
|
60
60
|
- exe/eager_eye
|
|
61
|
+
- images/icon.png
|
|
61
62
|
- lib/eager_eye.rb
|
|
62
63
|
- lib/eager_eye/analyzer.rb
|
|
63
64
|
- lib/eager_eye/auto_fixer.rb
|