eager_eye 1.2.10 → 1.2.12
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 +19 -0
- data/README.md +1 -1
- data/lib/eager_eye/analyzer.rb +1 -1
- data/lib/eager_eye/detectors/base.rb +12 -0
- data/lib/eager_eye/detectors/callback_query.rb +1 -1
- data/lib/eager_eye/detectors/count_in_iteration.rb +1 -1
- data/lib/eager_eye/detectors/custom_method_query.rb +3 -2
- data/lib/eager_eye/detectors/delegation_n_plus_one.rb +1 -1
- data/lib/eager_eye/detectors/loop_association.rb +3 -2
- data/lib/eager_eye/detectors/missing_counter_cache.rb +1 -1
- data/lib/eager_eye/detectors/scope_chain_n_plus_one.rb +1 -1
- data/lib/eager_eye/detectors/validation_n_plus_one.rb +1 -1
- data/lib/eager_eye/fixer_registry.rb +3 -1
- data/lib/eager_eye/version.rb +1 -1
- data/lib/eager_eye.rb +2 -0
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: d6d4e056fa0d51ee629c031675f0e1b2ae2de62a1ebde0161f9d8d3d6f860f90
|
|
4
|
+
data.tar.gz: e8c8239cc5017c68b1d4b91279a212086b1741493327eea0bc0059622de83116
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: deb89e2a0cedaa0d0fc41874a124725c1403634a105f157e66c52b6f80c63d02bf120bd4aa43765b452951b648972ad442deea3ca970d5355c4f79c34a3b5def
|
|
7
|
+
data.tar.gz: dcdbff4d567c605f0525c56d1e205e33e9383098eb30428bd336913917367afa00ff3b3bae893ba3b961d7bcb069528b28d44dd4a9613904aff29344bdfd8865
|
data/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,25 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [1.2.12] - 2026-03-20
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- **`.jbuilder` File Support** - Analyzer now scans `.jbuilder` files for N+1 queries
|
|
15
|
+
- `json.array!` blocks are recognized as iteration patterns across all detectors
|
|
16
|
+
- Existing detectors (LoopAssociation, CountInIteration, etc.) work seamlessly on jbuilder views
|
|
17
|
+
- **`CountToSize` Auto-fix** - Automatically replaces `.count` with `.size` inside iteration blocks
|
|
18
|
+
- **`AddIncludes` Auto-fix** - Suggests and applies `.includes(:association)` before iteration calls
|
|
19
|
+
|
|
20
|
+
## [1.2.11] - 2026-03-17
|
|
21
|
+
|
|
22
|
+
### Added
|
|
23
|
+
|
|
24
|
+
- **`each_with_object` / `reduce` / `inject` Support** - `LoopAssociation` and `CustomMethodQuery` detectors now recognize these iteration methods
|
|
25
|
+
- `each_with_object` and `each_with_index` blocks are detected (item is first param)
|
|
26
|
+
- `reduce` and `inject` blocks are detected with correct variable extraction (item is second param: `|memo, item|`)
|
|
27
|
+
- `extract_iteration_variable` helper added to `Base` for reuse across detectors
|
|
28
|
+
|
|
10
29
|
## [1.2.10] - 2026-03-16
|
|
11
30
|
|
|
12
31
|
### Added
|
data/README.md
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
|
|
11
11
|
<p align="center">
|
|
12
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.2.
|
|
13
|
+
<a href="https://rubygems.org/gems/eager_eye"><img src="https://img.shields.io/badge/gem-v1.2.12-red.svg" alt="Gem Version"></a>
|
|
14
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
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
16
|
<a href="https://opensource.org/licenses/MIT"><img src="https://img.shields.io/badge/License-MIT-yellow.svg" alt="License: MIT"></a>
|
data/lib/eager_eye/analyzer.rb
CHANGED
|
@@ -99,7 +99,7 @@ module EagerEye
|
|
|
99
99
|
|
|
100
100
|
def resolve_path(path)
|
|
101
101
|
return [path] if File.file?(path)
|
|
102
|
-
return Dir.glob(File.join(path, "**", "*.rb")) if File.directory?(path)
|
|
102
|
+
return Dir.glob(File.join(path, "**", "*.{rb,jbuilder}")) if File.directory?(path)
|
|
103
103
|
|
|
104
104
|
Dir.glob(path)
|
|
105
105
|
end
|
|
@@ -73,12 +73,24 @@ module EagerEye
|
|
|
73
73
|
end
|
|
74
74
|
end
|
|
75
75
|
|
|
76
|
+
ACCUMULATOR_FIRST_METHODS = %i[reduce inject].freeze
|
|
77
|
+
|
|
76
78
|
def extract_block_variable(block_node)
|
|
77
79
|
args = block_node&.children&.[](1)
|
|
78
80
|
first_arg = args&.children&.first
|
|
79
81
|
first_arg&.type == :arg ? first_arg.children[0] : nil
|
|
80
82
|
end
|
|
81
83
|
|
|
84
|
+
def extract_iteration_variable(block_node)
|
|
85
|
+
idx = accumulator_first?(block_node) ? 1 : 0
|
|
86
|
+
arg = block_node.children[1]&.children&.[](idx)
|
|
87
|
+
arg&.type == :arg ? arg.children[0] : nil
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def accumulator_first?(block_node)
|
|
91
|
+
ACCUMULATOR_FIRST_METHODS.include?(block_node.children[0]&.children&.[](1))
|
|
92
|
+
end
|
|
93
|
+
|
|
82
94
|
def receiver_chain_starts_with?(node, target_var)
|
|
83
95
|
return false unless node.is_a?(Parser::AST::Node)
|
|
84
96
|
|
|
@@ -27,7 +27,7 @@ module EagerEye
|
|
|
27
27
|
].freeze
|
|
28
28
|
|
|
29
29
|
ITERATION_METHODS = %i[each map select find_all reject collect
|
|
30
|
-
find_each find_in_batches in_batches].freeze
|
|
30
|
+
find_each find_in_batches in_batches array!].freeze
|
|
31
31
|
AR_BATCH_METHODS = %i[find_each find_in_batches in_batches].freeze
|
|
32
32
|
NON_AR_NAMESPACES = %w[Sidekiq Redis ActionCable ActionMailer Kafka].freeze
|
|
33
33
|
TRANSACTIONAL_CALLBACKS = %i[before_validation before_save before_create before_update before_destroy
|
|
@@ -5,7 +5,7 @@ module EagerEye
|
|
|
5
5
|
class CountInIteration < Base
|
|
6
6
|
COUNT_METHODS = %i[count].freeze
|
|
7
7
|
ITERATION_METHODS = %i[each map select find_all reject collect each_with_index each_with_object flat_map
|
|
8
|
-
find_each find_in_batches in_batches].freeze
|
|
8
|
+
find_each find_in_batches in_batches array!].freeze
|
|
9
9
|
ARRAY_METHOD_SUFFIXES = %w[_ids _tags _types _codes _names _values].freeze
|
|
10
10
|
|
|
11
11
|
def self.detector_name
|
|
@@ -9,7 +9,8 @@ module EagerEye
|
|
|
9
9
|
SAFE_TRANSFORM_METHODS = %i[keys values split [] params sort pluck ids to_s to_a to_i chars bytes].freeze
|
|
10
10
|
ARRAY_COLUMN_SUFFIXES = %w[_ids _tags _types _codes _names _values _arr].freeze
|
|
11
11
|
ITERATION_METHODS = %i[each map select find_all reject collect detect find_index flat_map
|
|
12
|
-
|
|
12
|
+
each_with_index each_with_object reduce inject
|
|
13
|
+
find_each find_in_batches in_batches array!].freeze
|
|
13
14
|
|
|
14
15
|
def self.detector_name
|
|
15
16
|
:custom_method_query
|
|
@@ -38,7 +39,7 @@ module EagerEye
|
|
|
38
39
|
definitions[node.children[0]] = node.children[1] if node.type == :lvasgn
|
|
39
40
|
|
|
40
41
|
if iteration_block?(node)
|
|
41
|
-
block_var =
|
|
42
|
+
block_var = extract_iteration_variable(node)
|
|
42
43
|
block_body = node.children[2]
|
|
43
44
|
yield(block_body, block_var, node.children[0], definitions) if block_var && block_body
|
|
44
45
|
end
|
|
@@ -5,7 +5,7 @@ module EagerEye
|
|
|
5
5
|
class DelegationNPlusOne < Base
|
|
6
6
|
ITERATION_METHODS = %i[
|
|
7
7
|
each map collect select find_all reject filter filter_map flat_map
|
|
8
|
-
find_each find_in_batches in_batches
|
|
8
|
+
find_each find_in_batches in_batches array!
|
|
9
9
|
].freeze
|
|
10
10
|
PRELOAD_METHODS = %i[includes preload eager_load].freeze
|
|
11
11
|
|
|
@@ -4,7 +4,8 @@ module EagerEye
|
|
|
4
4
|
module Detectors
|
|
5
5
|
class LoopAssociation < Base
|
|
6
6
|
ITERATION_METHODS = %i[each map collect select find find_all reject filter filter_map flat_map
|
|
7
|
-
|
|
7
|
+
each_with_index each_with_object reduce inject
|
|
8
|
+
find_each find_in_batches in_batches array!].freeze
|
|
8
9
|
PRELOAD_METHODS = %i[includes preload eager_load].freeze
|
|
9
10
|
SINGLE_RECORD_METHODS = %i[find find_by find_by! first first! last last! take take! second third fourth fifth
|
|
10
11
|
forty_two sole find_sole_by].freeze
|
|
@@ -42,7 +43,7 @@ module EagerEye
|
|
|
42
43
|
traverse_ast(ast) do |node|
|
|
43
44
|
next unless iteration_block?(node)
|
|
44
45
|
|
|
45
|
-
block_var =
|
|
46
|
+
block_var = extract_iteration_variable(node)
|
|
46
47
|
next unless block_var
|
|
47
48
|
|
|
48
49
|
block_body = node.children[2]
|
|
@@ -11,7 +11,7 @@ module EagerEye
|
|
|
11
11
|
].freeze
|
|
12
12
|
ITERATION_METHODS = %i[each map collect select reject find_all filter filter_map flat_map
|
|
13
13
|
each_with_index each_with_object reduce inject sum
|
|
14
|
-
find_each find_in_batches in_batches].freeze
|
|
14
|
+
find_each find_in_batches in_batches array!].freeze
|
|
15
15
|
|
|
16
16
|
def self.detector_name
|
|
17
17
|
:missing_counter_cache
|
|
@@ -4,7 +4,7 @@ module EagerEye
|
|
|
4
4
|
module Detectors
|
|
5
5
|
class ScopeChainNPlusOne < Base
|
|
6
6
|
ITERATION_METHODS = %i[each map select find_all reject collect detect find_index flat_map
|
|
7
|
-
find_each find_in_batches in_batches].freeze
|
|
7
|
+
find_each find_in_batches in_batches array!].freeze
|
|
8
8
|
|
|
9
9
|
def self.detector_name
|
|
10
10
|
:scope_chain_n_plus_one
|
|
@@ -4,7 +4,7 @@ module EagerEye
|
|
|
4
4
|
module Detectors
|
|
5
5
|
class ValidationNPlusOne < Base
|
|
6
6
|
ITERATION_METHODS = %i[each map select find_all reject collect detect find_index flat_map
|
|
7
|
-
find_each find_in_batches in_batches].freeze
|
|
7
|
+
find_each find_in_batches in_batches array!].freeze
|
|
8
8
|
CREATE_METHODS = %i[create create!].freeze
|
|
9
9
|
SAVE_METHODS = %i[save save!].freeze
|
|
10
10
|
|
|
@@ -3,7 +3,9 @@
|
|
|
3
3
|
module EagerEye
|
|
4
4
|
class FixerRegistry
|
|
5
5
|
FIXERS = {
|
|
6
|
-
pluck_to_array: Fixers::PluckToSelect
|
|
6
|
+
pluck_to_array: Fixers::PluckToSelect,
|
|
7
|
+
count_in_iteration: Fixers::CountToSize,
|
|
8
|
+
loop_association: Fixers::AddIncludes
|
|
7
9
|
}.freeze
|
|
8
10
|
|
|
9
11
|
def self.fixer_for(issue, source_code)
|
data/lib/eager_eye/version.rb
CHANGED
data/lib/eager_eye.rb
CHANGED
|
@@ -25,6 +25,8 @@ require_relative "eager_eye/comment_parser"
|
|
|
25
25
|
require_relative "eager_eye/analyzer"
|
|
26
26
|
require_relative "eager_eye/fixers/base"
|
|
27
27
|
require_relative "eager_eye/fixers/pluck_to_select"
|
|
28
|
+
require_relative "eager_eye/fixers/count_to_size"
|
|
29
|
+
require_relative "eager_eye/fixers/add_includes"
|
|
28
30
|
require_relative "eager_eye/fixer_registry"
|
|
29
31
|
require_relative "eager_eye/auto_fixer"
|
|
30
32
|
require_relative "eager_eye/reporters/base"
|
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.2.
|
|
4
|
+
version: 1.2.12
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- hamzagedikkaya
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-03-
|
|
11
|
+
date: 2026-03-19 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: ast
|