eager_eye 1.0.10 → 1.1.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.
@@ -3,26 +3,10 @@
3
3
  module EagerEye
4
4
  module Detectors
5
5
  class SerializerNesting < Base
6
- # Serializer base classes to detect
7
- SERIALIZER_PATTERNS = [
8
- "ActiveModel::Serializer",
9
- "ActiveModelSerializers::Model",
10
- "Blueprinter::Base",
11
- "Alba::Resource"
12
- ].freeze
13
-
14
- # Method names that define attributes in serializers
6
+ SERIALIZER_PATTERNS = %w[ActiveModel::Serializer ActiveModelSerializers::Model Blueprinter::Base Alba::Resource].freeze
15
7
  ATTRIBUTE_METHODS = %i[attribute field attributes].freeze
16
-
17
- # Object reference names in serializers
18
8
  OBJECT_REFS = %i[object record resource].freeze
19
-
20
- # Common association names (same as LoopAssociation)
21
- ASSOCIATION_NAMES = %w[
22
- author user owner creator admin member customer client
23
- post article comment category tag parent company organization
24
- project task item order product account profile setting
25
- image avatar photo attachment document
9
+ HAS_MANY_ASSOCIATIONS = %w[
26
10
  authors users owners creators admins members customers clients
27
11
  posts articles comments categories tags children companies organizations
28
12
  projects tasks items orders products accounts profiles settings
@@ -52,21 +36,16 @@ module EagerEye
52
36
  def serializer_class?(node)
53
37
  return false unless node.type == :class
54
38
 
55
- # Check class name ends with Serializer, Blueprint, or Resource
56
39
  class_name = extract_class_name(node)
57
40
  return false unless class_name
58
41
 
59
42
  class_name.end_with?("Serializer", "Blueprint", "Resource") ||
60
- inherits_from_serializer?(node) ||
61
- includes_serializer_module?(node)
43
+ inherits_from_serializer?(node) || includes_serializer_module?(node)
62
44
  end
63
45
 
64
46
  def extract_class_name(class_node)
65
47
  name_node = class_node.children[0]
66
- return nil unless name_node
67
- return nil unless name_node.type == :const
68
-
69
- name_node.children[1].to_s
48
+ name_node.children[1].to_s if name_node&.type == :const
70
49
  end
71
50
 
72
51
  def inherits_from_serializer?(class_node)
@@ -74,7 +53,7 @@ module EagerEye
74
53
  return false unless parent_node
75
54
 
76
55
  parent_name = const_to_string(parent_node)
77
- SERIALIZER_PATTERNS.any? { |pattern| parent_name&.include?(pattern.split("::").last) }
56
+ SERIALIZER_PATTERNS.any? { |p| parent_name&.include?(p.split("::").last) }
78
57
  end
79
58
 
80
59
  def includes_serializer_module?(class_node)
@@ -82,20 +61,14 @@ module EagerEye
82
61
  return false unless body
83
62
 
84
63
  traverse_ast(body) do |node|
85
- next unless node.type == :send
86
-
87
- method = node.children[1]
88
- return true if method == :include && alba_resource?(node)
64
+ return true if node.type == :send && node.children[1] == :include && alba_resource?(node)
89
65
  end
90
-
91
66
  false
92
67
  end
93
68
 
94
69
  def alba_resource?(include_node)
95
70
  arg = include_node.children[2]
96
- return false unless arg
97
-
98
- const_to_string(arg)&.include?("Alba")
71
+ arg && const_to_string(arg)&.include?("Alba")
99
72
  end
100
73
 
101
74
  def const_to_string(node)
@@ -103,12 +76,10 @@ module EagerEye
103
76
 
104
77
  parts = []
105
78
  current = node
106
-
107
79
  while current&.type == :const
108
80
  parts.unshift(current.children[1].to_s)
109
81
  current = current.children[0]
110
82
  end
111
-
112
83
  parts.join("::")
113
84
  end
114
85
 
@@ -117,34 +88,24 @@ module EagerEye
117
88
  return unless body
118
89
 
119
90
  traverse_ast(body) do |node|
120
- next unless attribute_block?(node)
121
-
122
- block_body = node.children[2]
123
- next unless block_body
91
+ next unless attribute_block?(node) && node.children[2]
124
92
 
125
- find_association_in_block(block_body, node, file_path, issues)
93
+ find_association_in_block(node.children[2], file_path, issues)
126
94
  end
127
95
  end
128
96
 
129
97
  def attribute_block?(node)
130
- return false unless node.type == :block
131
-
132
- send_node = node.children[0]
133
- return false unless send_node&.type == :send
134
-
135
- method_name = send_node.children[1]
136
- ATTRIBUTE_METHODS.include?(method_name)
98
+ node.type == :block && node.children[0]&.type == :send &&
99
+ ATTRIBUTE_METHODS.include?(node.children[0].children[1])
137
100
  end
138
101
 
139
- def find_association_in_block(block_body, _block_node, file_path, issues)
102
+ def find_association_in_block(block_body, file_path, issues)
140
103
  traverse_ast(block_body) do |node|
141
104
  next unless node.type == :send
142
105
 
143
106
  receiver = node.children[0]
144
107
  method_name = node.children[1]
145
-
146
- next unless object_reference?(receiver)
147
- next unless likely_association?(method_name)
108
+ next unless object_reference?(receiver) && likely_association?(method_name)
148
109
 
149
110
  issues << create_issue(
150
111
  file_path: file_path,
@@ -159,33 +120,22 @@ module EagerEye
159
120
  return false unless node
160
121
 
161
122
  case node.type
162
- when :send
163
- # object.something or record.something
164
- receiver = node.children[0]
165
- method = node.children[1]
166
-
167
- receiver.nil? && OBJECT_REFS.include?(method)
168
- when :lvar
169
- # Block variable like |post|
170
- true
171
- else
172
- false
123
+ when :send then node.children[0].nil? && OBJECT_REFS.include?(node.children[1])
124
+ when :lvar then true
125
+ else false
173
126
  end
174
127
  end
175
128
 
176
129
  def receiver_name(node)
177
130
  case node.type
178
- when :send
179
- node.children[1].to_s
180
- when :lvar
181
- node.children[0].to_s
182
- else
183
- "object"
131
+ when :send then node.children[1].to_s
132
+ when :lvar then node.children[0].to_s
133
+ else "object"
184
134
  end
185
135
  end
186
136
 
187
137
  def likely_association?(method_name)
188
- ASSOCIATION_NAMES.include?(method_name.to_s)
138
+ HAS_MANY_ASSOCIATIONS.include?(method_name.to_s)
189
139
  end
190
140
  end
191
141
  end
@@ -3,27 +3,20 @@
3
3
  module EagerEye
4
4
  module Fixers
5
5
  class PluckToSelect < Base
6
- # This fixer only works for single-line pluck + where patterns
7
- # Two-line patterns are too complex to fix automatically
8
-
9
6
  def fixable?
10
- issue.detector == :pluck_to_array &&
11
- single_line_pattern?
7
+ issue.detector == :pluck_to_array && single_line_pattern?
12
8
  end
13
9
 
14
10
  protected
15
11
 
16
12
  def fixed_content
17
- # Model.where(col: OtherModel.pluck(:id)) -> Model.where(col: OtherModel.select(:id))
18
13
  line_content.gsub(/\.pluck\((:\w+)\)/, '.select(\1)')
19
14
  end
20
15
 
21
16
  private
22
17
 
23
18
  def single_line_pattern?
24
- return false unless line_content
25
-
26
- line_content.include?(".pluck(") && line_content.include?(".where(")
19
+ line_content&.include?(".pluck(") && line_content.include?(".where(")
27
20
  end
28
21
  end
29
22
  end
@@ -40,20 +40,13 @@ module EagerEye
40
40
  end
41
41
 
42
42
  def ==(other)
43
- return false unless other.is_a?(Issue)
44
-
45
- detector == other.detector &&
46
- file_path == other.file_path &&
47
- line_number == other.line_number &&
48
- message == other.message &&
49
- severity == other.severity &&
50
- suggestion == other.suggestion
43
+ other.is_a?(Issue) && to_h == other.to_h
51
44
  end
52
45
 
53
46
  alias eql? ==
54
47
 
55
48
  def hash
56
- [detector, file_path, line_number, message, severity, suggestion].hash
49
+ to_h.hash
57
50
  end
58
51
 
59
52
  private
@@ -10,32 +10,20 @@ module EagerEye
10
10
  namespace :eager_eye do
11
11
  desc "Analyze Rails application for N+1 query issues"
12
12
  task analyze: :environment do
13
- require "eager_eye"
14
-
15
- load_config_file
16
-
17
- analyzer = EagerEye::Analyzer.new
18
- issues = analyzer.run
19
-
20
- reporter = EagerEye::Reporters::Console.new(issues)
21
- puts reporter.report
22
-
23
- exit 1 if issues.any? && EagerEye.configuration.fail_on_issues
13
+ puts run_analysis(EagerEye::Reporters::Console)
24
14
  end
25
15
 
26
16
  desc "Analyze and output results as JSON"
27
17
  task json: :environment do
28
- require "eager_eye"
18
+ puts run_analysis(EagerEye::Reporters::Json, pretty: true)
19
+ end
29
20
 
21
+ def run_analysis(reporter_class, **opts)
22
+ require "eager_eye"
30
23
  load_config_file
31
-
32
- analyzer = EagerEye::Analyzer.new
33
- issues = analyzer.run
34
-
35
- reporter = EagerEye::Reporters::Json.new(issues, pretty: true)
36
- puts reporter.report
37
-
24
+ issues = EagerEye::Analyzer.new.run
38
25
  exit 1 if issues.any? && EagerEye.configuration.fail_on_issues
26
+ reporter_class.new(issues, **opts).report
39
27
  end
40
28
 
41
29
  def load_config_file
@@ -55,7 +43,6 @@ module EagerEye
55
43
  end
56
44
  end
57
45
 
58
- # Generate initializer for configuration
59
46
  generators do
60
47
  require_relative "generators/install_generator"
61
48
  end
@@ -48,15 +48,7 @@ module EagerEye
48
48
  end
49
49
 
50
50
  def file_section(file_path, file_issues)
51
- lines = []
52
- lines << colorize(file_path, :cyan)
53
-
54
- file_issues.each do |issue|
55
- lines << format_issue(issue)
56
- end
57
-
58
- lines << ""
59
- lines.join("\n")
51
+ [colorize(file_path, :cyan), *file_issues.map { |i| format_issue(i) }, ""].join("\n")
60
52
  end
61
53
 
62
54
  def format_issue(issue)
@@ -81,15 +73,12 @@ module EagerEye
81
73
  end
82
74
 
83
75
  def summary
84
- total = issues.size
85
- errors = error_count
86
- warnings = warning_count
87
- infos = info_count
88
-
89
- "Total: #{total} issue#{"s" unless total == 1} " \
90
- "(#{errors} error#{"s" unless errors == 1}, " \
91
- "#{warnings} warning#{"s" unless warnings == 1}, " \
92
- "#{infos} info)"
76
+ t = issues.size
77
+ e = error_count
78
+ w = warning_count
79
+ i = info_count
80
+ "Total: #{t} issue#{"s" unless t == 1} " \
81
+ "(#{e} error#{"s" unless e == 1}, #{w} warning#{"s" unless w == 1}, #{i} info)"
93
82
  end
94
83
 
95
84
  def colorize(text, color)
@@ -19,8 +19,7 @@ module EagerEye
19
19
  def matches?(path)
20
20
  @path = path
21
21
  configure_eager_eye
22
- analyzer = build_analyzer
23
- @issues = analyzer.run
22
+ @issues = EagerEye::Analyzer.new(paths: [@path]).run
24
23
  @issues.count <= @max_issues
25
24
  end
26
25
 
@@ -61,10 +60,6 @@ module EagerEye
61
60
  config.fail_on_issues = false
62
61
  end
63
62
  end
64
-
65
- def build_analyzer
66
- EagerEye::Analyzer.new(paths: [@path])
67
- end
68
63
  end
69
64
  end
70
65
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module EagerEye
4
- VERSION = "1.0.10"
4
+ VERSION = "1.1.1"
5
5
  end
data/lib/eager_eye.rb CHANGED
@@ -3,6 +3,7 @@
3
3
  require_relative "eager_eye/version"
4
4
  require_relative "eager_eye/configuration"
5
5
  require_relative "eager_eye/issue"
6
+ require_relative "eager_eye/association_parser"
6
7
  require_relative "eager_eye/detectors/base"
7
8
  require_relative "eager_eye/detectors/loop_association"
8
9
  require_relative "eager_eye/detectors/serializer_nesting"
@@ -40,5 +41,4 @@ module EagerEye
40
41
  end
41
42
  end
42
43
 
43
- # Load Railtie only if Rails is defined
44
44
  require_relative "eager_eye/railtie" if defined?(Rails::Railtie)
data/sig/eager_eye.rbs CHANGED
@@ -1,4 +1,3 @@
1
1
  module EagerEye
2
2
  VERSION: String
3
- # See the writing guide of rbs: https://github.com/ruby/rbs#guides
4
3
  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.10
4
+ version: 1.1.1
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-26 00:00:00.000000000 Z
11
+ date: 2026-01-03 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: ast
@@ -59,6 +59,7 @@ files:
59
59
  - exe/eager_eye
60
60
  - lib/eager_eye.rb
61
61
  - lib/eager_eye/analyzer.rb
62
+ - lib/eager_eye/association_parser.rb
62
63
  - lib/eager_eye/auto_fixer.rb
63
64
  - lib/eager_eye/cli.rb
64
65
  - lib/eager_eye/comment_parser.rb