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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1e198460506f7e19906eeed320e9f9f11987ca1f4894b4b205a56d58799f3781
4
- data.tar.gz: e61be4439b88d9731678fae64cb530390c7d2ab06dede3bbef570f8923c5542c
3
+ metadata.gz: f19a4d72ee31d2ea100782d4fd9da6594db5f9ec78e9290723461441f58e264d
4
+ data.tar.gz: d8b347ecb77010db7ebff3db74610de1516c653991d7264321f302d2b3eaf0ea
5
5
  SHA512:
6
- metadata.gz: b32e0853b58fd412511418aedf8ce2d452d0f5a20b20e54eb3c2bb260c7233af7f7bbbe465f7efd7b511114152ffb72b381f54b88bd12c67463a63b547f6ff91
7
- data.tar.gz: bf371dcf0600ec9f57614eae0d2d52fee4a904f3be37bd5d2f8a0e8d7066135d78b86d6a914e90f61fbc26afcee09003f886c8415ce2d84ce3d35799825975ee
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
- # EagerEye
1
+ <p align="center">
2
+ <img src="images/icon.png" alt="EagerEye Logo" width="140">
3
+ </p>
2
4
 
3
- [![CI](https://github.com/hamzagedikkaya/eager_eye/actions/workflows/main.yml/badge.svg)](https://github.com/hamzagedikkaya/eager_eye/actions/workflows/main.yml)
4
- [![Gem Version](https://img.shields.io/badge/gem-v1.0.5-red.svg)](https://rubygems.org/gems/eager_eye)
5
- [![Coverage](https://img.shields.io/badge/coverage-95%25-brightgreen.svg)](https://github.com/hamzagedikkaya/eager_eye)
6
- [![Ruby](https://img.shields.io/badge/ruby-%3E%3D%203.1-ruby.svg)](https://www.ruby-lang.org/)
7
- [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
8
- [![VS Code Extension](https://img.shields.io/badge/VS%20Code-Extension-blue.svg)](https://marketplace.visualstudio.com/items?itemName=hamzagedikkaya.eager-eye)
5
+ <h1 align="center">EagerEye</h1>
9
6
 
10
- **Static analysis tool for detecting N+1 queries in Rails applications.**
7
+ <p align="center">
8
+ <strong>Static analysis tool for detecting N+1 queries in Rails applications.</strong>
9
+ </p>
11
10
 
12
- EagerEye analyzes your Ruby code without running it, using AST (Abstract Syntax Tree) parsing to find potential N+1 query issues before they hit production.
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` or `.size` calls on associations that could benefit from counter caches.
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 every time
157
- post.comments.count
158
- post.comments.size
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
- # Good - Add counter cache
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.comments_count
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module EagerEye
4
- VERSION = "1.0.5"
4
+ VERSION = "1.0.7"
5
5
  end
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.5
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-21 00:00:00.000000000 Z
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