rubosquad 0.4.1 → 0.5.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.
Files changed (3) hide show
  1. checksums.yaml +4 -4
  2. data/lib/rubosquad.rb +117 -16
  3. metadata +1 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b4122eef7f956af0412b99e4bb2f220ce2afc33a810b77ec2280244eba9e61ec
4
- data.tar.gz: dd98295ce280108341cfd86deb9f85a97298011517a1879a7a17fc1f8b634470
3
+ metadata.gz: bdc45fe50d0678e9dd9f45bc7967aeea4ed6b3ee830593875d9ad8911f277592
4
+ data.tar.gz: 784949c827c7d611bf125f791cf3d5913f93a9b508efc1dcb0814c3d3350a607
5
5
  SHA512:
6
- metadata.gz: c6f8fdca8ada9f7020a7f2544f15ef1c5ec61ba3bc085033b42820c2cba27c1a9c7fa015ca2f8aea156f7e22894542995573155f981757e815ca5fb69b3ed61d
7
- data.tar.gz: a46e355706bd10b40498712bf2c80f1179e9be08fb804dff81f76391c1a6311092a94325f751fbc6ca7555c9fce78a2167be6ab7e1148714485cc6b3afad07fc
6
+ metadata.gz: 929f4fea15d4ab2e7396f4a1b7ba159734bad3ba7d9d889f72de7c668bf5b47981028fa24971b263afe5b2655a1a74d4c651fae5d4250cb7c0a06a02eedbe031
7
+ data.tar.gz: c62933394e08b6520233aec0180384ec8367ac9de7c84f42e7c195a05ca888e91fc23881ef75ac4b2f0997a311657e47a4e75aae52ee35a398f6aa0d2892e6e8
data/lib/rubosquad.rb CHANGED
@@ -6,6 +6,13 @@ require 'optparse'
6
6
 
7
7
  module Rubosquad
8
8
  class CLI
9
+ OFFENSE_COLORS = {
10
+ 'E' => "\e[31m",
11
+ 'F' => "\e[31m",
12
+ 'W' => "\e[33m"
13
+ }.freeze
14
+ RESET = "\e[0m"
15
+
9
16
  class << self
10
17
  def run
11
18
  options = parse_options
@@ -24,6 +31,8 @@ module Rubosquad
24
31
  exit 0
25
32
  end
26
33
 
34
+ options[:base_branch] = base_branch
35
+ options[:current_branch] = current_branch
27
36
  print_file_list(files, current_branch, base_branch)
28
37
  run_rubocop(options, files)
29
38
  end
@@ -48,6 +57,12 @@ module Rubosquad
48
57
  opts.on('-A', '--unsafe-auto-correct', 'Run with rubocop -A (unsafe auto-corrections) instead of -a') do
49
58
  options[:unsafe_auto_correct] = true
50
59
  end
60
+ opts.on('-v', '--verbose', 'Show full stderr including new-cops notices and extra detail') do
61
+ options[:verbose] = true
62
+ end
63
+ opts.on('--all', 'Report all offenses, including pre-existing ones not in this branch') do
64
+ options[:all] = true
65
+ end
51
66
  end.parse!
52
67
  options
53
68
  end
@@ -112,20 +127,69 @@ module Rubosquad
112
127
  cmd = build_rubocop_cmd(options, extensions, files)
113
128
  stdout, stderr, status = Open3.capture3(*cmd)
114
129
 
130
+ changed_lines = options[:all] ? nil : parse_changed_lines(options, files)
131
+
115
132
  puts "\nRubocop Output:"
116
133
  puts '==Error type: Legend: C = Convention, W = Warning, E = Error, F = Fatal=='
117
- puts format_output(stdout)
134
+ formatted = format_output(stdout, changed_lines: changed_lines)
135
+ puts formatted
136
+
137
+ print_rubocop_result(stdout, stderr, status, extensions, options)
138
+
139
+ exit 0 if !status.success? && changed_lines && count_new_offenses(formatted).zero?
118
140
 
119
- print_rubocop_result(stdout, stderr, status, extensions)
120
141
  exit status.exitstatus unless status.success?
121
142
  end
122
143
 
144
+ def parse_changed_lines(options, files)
145
+ base_branch = options[:base_branch]
146
+ current_branch = options[:current_branch]
147
+
148
+ files.each_with_object({}) do |file, changed|
149
+ diff_text = if current_branch == base_branch
150
+ fetch_base_branch_diff(file)
151
+ else
152
+ fetch_feature_branch_diff(base_branch, file)
153
+ end
154
+ changed[file] = parse_hunk_lines(diff_text)
155
+ end
156
+ end
157
+
158
+ def fetch_feature_branch_diff(base_branch, file)
159
+ stdout, = Open3.capture3('git', 'diff', '-U0', "#{base_branch}...HEAD", '--', file)
160
+ stdout
161
+ end
162
+
163
+ def fetch_base_branch_diff(file)
164
+ unstaged, = Open3.capture3('git', 'diff', '-U0', 'HEAD', '--', file)
165
+ staged, = Open3.capture3('git', 'diff', '-U0', '--cached', '--', file)
166
+ "#{unstaged}#{staged}"
167
+ end
168
+
169
+ def parse_hunk_lines(diff_text)
170
+ lines = Set.new
171
+ diff_text.scan(/@@ -\d+(?:,\d+)? \+(\d+)(?:,(\d+))? @@/) do |start, count|
172
+ start = start.to_i
173
+ count = count.nil? ? 1 : count.to_i
174
+ count.times { |i| lines << (start + i) } if count.positive?
175
+ end
176
+ lines
177
+ end
178
+
179
+ def count_new_offenses(formatted_output)
180
+ formatted_output.lines.count { |l| l.match?(/^\[.+:\d+:\d+\] -/) }
181
+ end
182
+
123
183
  def detect_extensions
124
184
  %w[rubocop-factory_bot rubocop-rails rubocop-rspec rubocop-rspec_rails].select do |ext|
125
185
  Gem::Specification.find_by_name(ext)
186
+ require ext
126
187
  true
127
188
  rescue Gem::LoadError
128
189
  false
190
+ rescue LoadError => e
191
+ warn "Warning: #{ext} is installed but could not be loaded (#{e.message}). Skipping."
192
+ false
129
193
  end
130
194
  end
131
195
 
@@ -144,7 +208,7 @@ module Rubosquad
144
208
  false
145
209
  end
146
210
 
147
- def format_output(stdout)
211
+ def format_output(stdout, changed_lines: nil)
148
212
  lines = []
149
213
  seen_files = Set.new
150
214
  current_file = nil
@@ -153,13 +217,10 @@ module Rubosquad
153
217
  if line.start_with?('==')
154
218
  file_path = line.strip.gsub(/^==\s*|\s*==$/, '')
155
219
  current_file = file_path
156
- lines << (seen_files.add?(file_path) ? "\n\e[36m#{line}\e[0m" : "\n#{line}")
220
+ lines << (seen_files.add?(file_path) ? "\n\e[36m#{line.chomp}\e[0m\n" : "\n#{line}")
157
221
  elsif line.match?(/^[A-Z]:/)
158
- parts = line.split(':')
159
- offense_type = parts.shift
160
- line_col = parts.shift(2).map(&:strip).join(':')
161
- message = parts.join(':').strip
162
- lines << "[#{current_file || 'unknown'}:#{line_col}] - #{offense_type}: #{message}\n"
222
+ offense = parse_offense_line(line, current_file, changed_lines)
223
+ lines << offense if offense
163
224
  else
164
225
  lines << line
165
226
  end
@@ -168,21 +229,61 @@ module Rubosquad
168
229
  lines.join
169
230
  end
170
231
 
171
- def print_rubocop_result(stdout, stderr, status, extensions)
232
+ def parse_offense_line(line, current_file, changed_lines)
233
+ parts = line.split(':')
234
+ offense_type = parts.shift
235
+ line_num = parts.first.strip.to_i
236
+ line_col = parts.shift(2).map(&:strip).join(':')
237
+ message = parts.join(':').strip
238
+
239
+ return nil if changed_lines && !changed_lines[current_file]&.include?(line_num)
240
+
241
+ type_str = if (color = OFFENSE_COLORS[offense_type])
242
+ "#{color}#{offense_type}#{RESET}"
243
+ else
244
+ offense_type
245
+ end
246
+
247
+ "[#{current_file || 'unknown'}:#{line_col}] - #{type_str}: #{message}\n"
248
+ end
249
+
250
+ def print_rubocop_result(stdout, stderr, status, extensions, options = {})
251
+ total = stdout.scan(/(\d+) offenses? detected/).flatten.map(&:to_i).sum
252
+ corrected = stdout.scan(/(\d+) offenses? corrected/).flatten.map(&:to_i).sum
253
+ remaining = total - corrected
254
+
172
255
  if status.success?
173
256
  puts "\nRubocop auto-corrections complete."
174
257
  else
175
258
  puts "\nRubocop completed with offenses:"
176
- total = stdout.scan(/(\d+) offense.* detected/).flatten.map(&:to_i).sum
177
259
  puts "Files inspected: #{stdout[/(\d+) files? inspected/, 1] || 'Unknown'}"
178
- puts "Total offenses detected: #{total}"
179
- unless stderr.strip.empty?
180
- puts "\nStandard error:"
181
- puts stderr
182
- end
183
260
  end
261
+
262
+ puts "#{corrected} auto-corrected | #{remaining} remaining" if total.positive?
263
+ display_stderr(stderr, options[:verbose])
184
264
  puts "\nRuboCop extensions used: #{extensions.join(', ')}" unless extensions.empty?
185
265
  end
266
+
267
+ def display_stderr(stderr, verbose)
268
+ filtered = verbose ? stderr : filter_new_cops_notice(stderr)
269
+ return if filtered.strip.empty?
270
+
271
+ puts "\nStandard error:"
272
+ puts filtered
273
+ end
274
+
275
+ def filter_new_cops_notice(stderr)
276
+ return stderr unless stderr.include?('opt-in to new cops')
277
+
278
+ lines = stderr.lines
279
+ start_idx = lines.index { |l| l.include?('opt-in to new cops') }
280
+ return stderr unless start_idx
281
+
282
+ end_idx = lines.index { |l| l.include?('docs.rubocop.org/rubocop/versioning') }
283
+ end_idx ||= lines.length - 1
284
+
285
+ (lines[0...start_idx] + lines[(end_idx + 1)..]).join
286
+ end
186
287
  end
187
288
  end
188
289
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rubosquad
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.1
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - LucasWaki