eager_eye 0.5.0 → 0.6.0

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: 031e0f3547538856718932cd6121ac990ad8c11a43682ec076f68bb591943d55
4
- data.tar.gz: 433563980a743704c361304dff56866fe4cc9795a1d6f927cd0b4a8fc0367c8f
3
+ metadata.gz: 34e0c9e78e18d9d8c9a6cad8ee96a91c89ac469d9dc662e43a51f09d361a3200
4
+ data.tar.gz: 90554fbbe09380116398c8139c6d09ca5851d1e12d2eb52c32cf85f8c8fdc76a
5
5
  SHA512:
6
- metadata.gz: 5c9f874001fb0c71eb8c1858ef2739181482992b7455925afc696acfa1b7bebfa91888d305936ab6189f64ea1542afd7844b95c4ba453218d0c706d40d17304b
7
- data.tar.gz: 93750c566392f234cb5009d30873130182a4e80722b9f29402b662caacc0c961a31a8c109937c378f9a04b3d0f6e134326a0edeb0d138255debc2c9fb4ceb9a3
6
+ metadata.gz: 86605c1632ed47aaae05e26d810c6611dac2f356503502b71a94a389b1d58148cbb5146e5e6cbc5e9f809f4b6bf2b78d983cbf8484c0bab6d1e9a046ab0a242b
7
+ data.tar.gz: bb3e07e2b8e799067aa475f6b8ffea6a303e797e3eac252f46f597eec751f42451f91fd97eacf024aae8a6129267ef2b425fdd26163a6b893714cb22c2d9ddfa
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
+ ## [0.6.0] - 2025-12-15
11
+
12
+ ### Added
13
+
14
+ - **Inline Suppression Comments** - RuboCop-style comment directives for suppressing false positives
15
+ - `# eager_eye:disable DetectorName` - Disable for single line (inline) or start block
16
+ - `# eager_eye:disable-next-line DetectorName` - Disable only the next line
17
+ - `# eager_eye:disable-file DetectorName` - Disable for entire file (must be in first 5 lines)
18
+ - `# eager_eye:enable DetectorName` - End a disable block
19
+ - Support for multiple detectors: `# eager_eye:disable LoopAssociation, CountInIteration`
20
+ - Support for reason comments: `# eager_eye:disable DetectorName -- reason here`
21
+ - `all` keyword to disable all detectors at once
22
+ - Both CamelCase (`LoopAssociation`) and snake_case (`loop_association`) detector names accepted
23
+
24
+ ### Changed
25
+
26
+ - Updated README with inline suppression documentation
27
+ - Added `CommentParser` module for parsing suppression directives
28
+
10
29
  ## [0.5.0] - 2025-12-15
11
30
 
12
31
  ### Added
data/README.md CHANGED
@@ -272,6 +272,50 @@ Post.where(user_id: User.active.select(:id))
272
272
  | `pluck` + `where` | 2 | ~80KB for IDs | ~45ms |
273
273
  | `select` subquery | 1 | None | ~20ms |
274
274
 
275
+ ## Inline Suppression
276
+
277
+ Suppress false positives using inline comments (RuboCop-style):
278
+
279
+ ```ruby
280
+ # Disable for single line
281
+ user.posts.count # eager_eye:disable CountInIteration
282
+
283
+ # Disable for next line
284
+ # eager_eye:disable-next-line LoopAssociation
285
+ @users.each { |u| u.profile }
286
+
287
+ # Disable block
288
+ # eager_eye:disable LoopAssociation, SerializerNesting
289
+ @users.each do |user|
290
+ user.posts.each { |p| p.author }
291
+ end
292
+ # eager_eye:enable LoopAssociation, SerializerNesting
293
+
294
+ # Disable entire file (must be in first 5 lines)
295
+ # eager_eye:disable-file CustomMethodQuery
296
+
297
+ # With reason
298
+ user.posts.count # eager_eye:disable CountInIteration -- using counter_cache
299
+
300
+ # Disable all detectors
301
+ # eager_eye:disable all
302
+ ```
303
+
304
+ ### Available Detector Names
305
+
306
+ Both CamelCase and snake_case formats are accepted:
307
+
308
+ | Detector | CamelCase | snake_case |
309
+ |----------|-----------|------------|
310
+ | Loop Association | `LoopAssociation` | `loop_association` |
311
+ | Serializer Nesting | `SerializerNesting` | `serializer_nesting` |
312
+ | Missing Counter Cache | `MissingCounterCache` | `missing_counter_cache` |
313
+ | Custom Method Query | `CustomMethodQuery` | `custom_method_query` |
314
+ | Count in Iteration | `CountInIteration` | `count_in_iteration` |
315
+ | Callback Query | `CallbackQuery` | `callback_query` |
316
+ | Pluck to Array | `PluckToArray` | `pluck_to_array` |
317
+ | All Detectors | `all` | `all` |
318
+
275
319
  ## Configuration
276
320
 
277
321
  ### Config File (.eager_eye.yml)
@@ -58,8 +58,16 @@ module EagerEye
58
58
  ast = parse_source(source)
59
59
  return unless ast
60
60
 
61
+ comment_parser = CommentParser.new(source)
62
+
61
63
  enabled_detectors.each do |detector|
62
64
  file_issues = detector.detect(ast, file_path)
65
+
66
+ # Filter suppressed issues
67
+ file_issues.reject! do |issue|
68
+ comment_parser.disabled_at?(issue.line_number, issue.detector)
69
+ end
70
+
63
71
  @issues.concat(file_issues)
64
72
  end
65
73
  rescue Errno::ENOENT, Errno::EACCES => e
@@ -0,0 +1,144 @@
1
+ # frozen_string_literal: true
2
+
3
+ module EagerEye
4
+ class CommentParser
5
+ DISABLE_PATTERN = /eager_eye:disable(?:-next-line|-file)?\s+(.+?)(?:\s+--|$)/i
6
+ ENABLE_PATTERN = /eager_eye:enable\s+(.+?)(?:\s+--|$)/i
7
+ INLINE_DISABLE_PATTERN = /eager_eye:disable\s+(.+?)(?:\s+--|$)/i
8
+ FILE_DISABLE_PATTERN = /eager_eye:disable-file\s+(.+?)(?:\s+--|$)/i
9
+ NEXT_LINE_PATTERN = /eager_eye:disable-next-line\s+(.+?)(?:\s+--|$)/i
10
+ BLOCK_DISABLE_PATTERN = /eager_eye:disable\s+(.+?)(?:\s+--|$)/i
11
+
12
+ def initialize(source_code)
13
+ @source_code = source_code
14
+ @lines = source_code.lines
15
+ @disabled_ranges = Hash.new { |h, k| h[k] = [] }
16
+ @file_disabled = Set.new
17
+ @current_disabled = Set.new
18
+ parse_comments
19
+ end
20
+
21
+ def disabled_at?(line_number, detector_name)
22
+ return true if @file_disabled.include?(detector_name.to_s)
23
+ return true if @file_disabled.include?("all")
24
+
25
+ detector = detector_name.to_s
26
+ @disabled_ranges[detector].any? { |range| range.cover?(line_number) } ||
27
+ @disabled_ranges["all"].any? { |range| range.cover?(line_number) }
28
+ end
29
+
30
+ private
31
+
32
+ def parse_comments
33
+ @lines.each_with_index do |line, index|
34
+ line_num = index + 1
35
+ process_line(line, line_num)
36
+ end
37
+
38
+ close_unclosed_blocks
39
+ end
40
+
41
+ def process_line(line, line_num)
42
+ directive = detect_directive(line, line_num)
43
+ apply_directive(directive, line_num) if directive
44
+ end
45
+
46
+ def detect_directive(line, line_num)
47
+ detect_file_directive(line, line_num) ||
48
+ detect_next_line_directive(line) ||
49
+ detect_block_directive(line) ||
50
+ detect_enable_directive(line) ||
51
+ detect_inline_directive(line)
52
+ end
53
+
54
+ def detect_file_directive(line, line_num)
55
+ return unless line_num <= 5 && line =~ FILE_DISABLE_PATTERN
56
+
57
+ { type: :file, detectors: parse_detector_names(::Regexp.last_match(1)) }
58
+ end
59
+
60
+ def detect_next_line_directive(line)
61
+ return unless line =~ NEXT_LINE_PATTERN
62
+
63
+ { type: :next_line, detectors: parse_detector_names(::Regexp.last_match(1)) }
64
+ end
65
+
66
+ def detect_block_directive(line)
67
+ return unless line =~ BLOCK_DISABLE_PATTERN && !inline_disable?(line)
68
+
69
+ { type: :block_start, detectors: parse_detector_names(::Regexp.last_match(1)) }
70
+ end
71
+
72
+ def detect_enable_directive(line)
73
+ return unless line =~ ENABLE_PATTERN
74
+
75
+ { type: :block_end, detectors: parse_detector_names(::Regexp.last_match(1)) }
76
+ end
77
+
78
+ def detect_inline_directive(line)
79
+ return unless line =~ INLINE_DISABLE_PATTERN
80
+
81
+ { type: :inline, detectors: parse_detector_names(::Regexp.last_match(1)) }
82
+ end
83
+
84
+ def apply_directive(directive, line_num)
85
+ case directive[:type]
86
+ when :file then apply_file_disable(directive[:detectors])
87
+ when :next_line then apply_next_line_disable(directive[:detectors], line_num)
88
+ when :block_start then apply_block_start(directive[:detectors], line_num)
89
+ when :block_end then apply_block_end(directive[:detectors], line_num)
90
+ when :inline then apply_inline_disable(directive[:detectors], line_num)
91
+ end
92
+ end
93
+
94
+ def apply_file_disable(detectors)
95
+ @file_disabled.merge(detectors)
96
+ end
97
+
98
+ def apply_next_line_disable(detectors, line_num)
99
+ next_line = line_num + 1
100
+ detectors.each { |d| @disabled_ranges[d] << (next_line..next_line) }
101
+ end
102
+
103
+ def apply_block_start(detectors, line_num)
104
+ detectors.each { |d| @current_disabled << { detector: d, start: line_num } }
105
+ end
106
+
107
+ def apply_block_end(detectors, line_num)
108
+ detectors.each do |d|
109
+ entry = @current_disabled.find { |e| e[:detector] == d }
110
+ next unless entry
111
+
112
+ @disabled_ranges[d] << (entry[:start]..line_num)
113
+ @current_disabled.delete(entry)
114
+ end
115
+ end
116
+
117
+ def apply_inline_disable(detectors, line_num)
118
+ detectors.each { |d| @disabled_ranges[d] << (line_num..line_num) }
119
+ end
120
+
121
+ def close_unclosed_blocks
122
+ @current_disabled.each do |entry|
123
+ @disabled_ranges[entry[:detector]] << (entry[:start]..@lines.size)
124
+ end
125
+ end
126
+
127
+ def inline_disable?(line)
128
+ code_part = line.split("#").first
129
+ code_part && !code_part.strip.empty?
130
+ end
131
+
132
+ def parse_detector_names(str)
133
+ str.split(/[,\s]+/).map(&:strip).reject(&:empty?).map do |name|
134
+ normalize_detector_name(name)
135
+ end
136
+ end
137
+
138
+ def normalize_detector_name(name)
139
+ name.gsub(/([A-Z])/) { "_#{::Regexp.last_match(1).downcase}" }
140
+ .sub(/^_/, "")
141
+ .downcase
142
+ end
143
+ end
144
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module EagerEye
4
- VERSION = "0.5.0"
4
+ VERSION = "0.6.0"
5
5
  end
data/lib/eager_eye.rb CHANGED
@@ -11,6 +11,7 @@ require_relative "eager_eye/detectors/custom_method_query"
11
11
  require_relative "eager_eye/detectors/count_in_iteration"
12
12
  require_relative "eager_eye/detectors/callback_query"
13
13
  require_relative "eager_eye/detectors/pluck_to_array"
14
+ require_relative "eager_eye/comment_parser"
14
15
  require_relative "eager_eye/analyzer"
15
16
  require_relative "eager_eye/reporters/base"
16
17
  require_relative "eager_eye/reporters/console"
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.5.0
4
+ version: 0.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - hamzagedikkaya
@@ -59,6 +59,7 @@ files:
59
59
  - lib/eager_eye.rb
60
60
  - lib/eager_eye/analyzer.rb
61
61
  - lib/eager_eye/cli.rb
62
+ - lib/eager_eye/comment_parser.rb
62
63
  - lib/eager_eye/configuration.rb
63
64
  - lib/eager_eye/detectors/base.rb
64
65
  - lib/eager_eye/detectors/callback_query.rb