Skip to content

Commit ebb4a53

Browse files
committed
Don't ignore RuboCop messages when output written to STDERR
If RuboCop is running on a version of Ruby that is older than the version `parser` gem is designed to parse, warnings such as: warning: parser/current is loading parser/ruby21, which recognizes warning: 2.1.7-compliant syntax, but you are running 2.1.1. ...are written to STDERR. Unfortunately, a fix for a different issue in efa40dc causes us to not output any cop messages when these `parser` gem warnings are emitted. Fix this by explicitly extracting the messages separately. This required adding support for mixing `Overcommit::Hook::Message`s with and without file/line information (previously all messages were assumed to have file/line information).
1 parent 338a360 commit ebb4a53

File tree

6 files changed

+154
-13
lines changed

6 files changed

+154
-13
lines changed

CHANGELOG.md

+2
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
* Add `--without-color` flag to `RailsBestPractices` pre-commit hook
99
to fix parsing issues due to color escape sequences
1010
* Improve error message when `gemfile` has not had a dependency installed
11+
* Fix `RuboCop` pre-commit hook to not swallow cop messages when `parser` gem
12+
warnings are output to STDERR
1113

1214
## 0.30.0
1315

lib/overcommit/hook/pre_commit/base.rb

+4-1
Original file line numberDiff line numberDiff line change
@@ -40,14 +40,17 @@ def extract_messages(output_messages, regex, type_categorizer = nil)
4040
end
4141

4242
def extract_file(match, message)
43-
if match[:file].nil? || match[:file].empty?
43+
return unless match.names.include?('file')
44+
45+
if match[:file].to_s.empty?
4446
raise "Unexpected output: no file found in '#{message}'"
4547
end
4648

4749
match[:file]
4850
end
4951

5052
def extract_line(match, message)
53+
return unless match.names.include?('line')
5154
Integer(match[:line])
5255
rescue ArgumentError, TypeError
5356
raise "Unexpected output: invalid line number found in '#{message}'"

lib/overcommit/hook/pre_commit/rubo_cop.rb

+15-4
Original file line numberDiff line numberDiff line change
@@ -3,20 +3,31 @@ module Overcommit::Hook::PreCommit
33
#
44
# @see http://batsov.com/rubocop/
55
class RuboCop < Base
6-
MESSAGE_TYPE_CATEGORIZER = lambda do |type|
6+
GENERIC_MESSAGE_TYPE_CATEGORIZER = lambda do |type|
7+
type =~ /^warn/ ? :warning : :error
8+
end
9+
10+
COP_MESSAGE_TYPE_CATEGORIZER = lambda do |type|
711
type.include?('W') ? :warning : :error
812
end
913

1014
def run
1115
result = execute(command, args: applicable_files)
1216
return :pass if result.success?
13-
return [:fail, result.stderr] unless result.stderr.empty?
1417

15-
extract_messages(
18+
generic_messages = extract_messages(
19+
result.stderr.split("\n"),
20+
/^(?<type>[a-z]+)/i,
21+
GENERIC_MESSAGE_TYPE_CATEGORIZER,
22+
)
23+
24+
cop_messages = extract_messages(
1625
result.stdout.split("\n"),
1726
/^(?<file>(?:\w:)?[^:]+):(?<line>\d+):[^ ]+ (?<type>[^ ]+)/,
18-
MESSAGE_TYPE_CATEGORIZER,
27+
COP_MESSAGE_TYPE_CATEGORIZER,
1928
)
29+
30+
generic_messages + cop_messages
2031
end
2132
end
2233
end

lib/overcommit/message_processor.rb

+14-3
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ class MessageProcessor
1212
WARNINGS_MODIFIED_HEADER = 'Warnings on modified lines:'.freeze
1313
ERRORS_UNMODIFIED_HEADER = "Errors on lines you didn't modify:".freeze
1414
WARNINGS_UNMODIFIED_HEADER = "Warnings on lines you didn't modify:".freeze
15+
ERRORS_GENERIC_HEADER = 'Errors:'.freeze
16+
WARNINGS_GENERIC_HEADER = 'Warnings:'.freeze
1517

1618
# @param hook [Overcommit::Hook::Base]
1719
# @param unmodified_lines_setting [String] how to treat messages on
@@ -42,10 +44,19 @@ def hook_result(messages)
4244
def handle_modified_lines(messages, status)
4345
messages = remove_ignored_messages(messages)
4446

45-
messages_on_modified_lines, messages_on_unmodified_lines =
46-
messages.partition { |message| message_on_modified_line?(message) }
47+
messages_with_line, generic_messages = messages.partition(&:line)
4748

49+
# Always print generic messages first
4850
output = print_messages(
51+
generic_messages,
52+
ERRORS_GENERIC_HEADER,
53+
WARNINGS_GENERIC_HEADER
54+
)
55+
56+
messages_on_modified_lines, messages_on_unmodified_lines =
57+
messages_with_line.partition { |message| message_on_modified_line?(message) }
58+
59+
output += print_messages(
4960
messages_on_modified_lines,
5061
ERRORS_MODIFIED_HEADER,
5162
WARNINGS_MODIFIED_HEADER
@@ -56,7 +67,7 @@ def handle_modified_lines(messages, status)
5667
WARNINGS_UNMODIFIED_HEADER
5768
)
5869

59-
[transform_status(status, messages_on_modified_lines), output]
70+
[transform_status(status, generic_messages + messages_on_modified_lines), output]
6071
end
6172

6273
def transform_status(status, messages_on_modified_lines)

spec/overcommit/hook/pre_commit/rubo_cop_spec.rb

+40-3
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,26 @@
1010
end
1111

1212
context 'when rubocop exits successfully' do
13+
let(:result) { double('result') }
14+
1315
before do
14-
result = double('result')
15-
result.stub(:success?).and_return(true)
16+
result.stub(success?: true, stderr: '', stdout: '')
1617
subject.stub(:execute).and_return(result)
1718
end
1819

1920
it { should pass }
21+
22+
context 'and it printed warnings to stderr' do
23+
before do
24+
result.stub(:stderr).and_return(normalize_indent(<<-MSG))
25+
warning: parser/current is loading parser/ruby21, which recognizes
26+
warning: 2.1.8-compliant syntax, but you are running 2.1.1.
27+
warning: please see https://github.com/whitequark/parser#compatibility-with-ruby-mri.
28+
MSG
29+
end
30+
31+
it { should pass }
32+
end
2033
end
2134

2235
context 'when rubocop exits unsucessfully' do
@@ -36,6 +49,18 @@
3649
end
3750

3851
it { should warn }
52+
53+
context 'and it printed warnings to stderr' do
54+
before do
55+
result.stub(:stderr).and_return(normalize_indent(<<-MSG))
56+
warning: parser/current is loading parser/ruby21, which recognizes
57+
warning: 2.1.8-compliant syntax, but you are running 2.1.1.
58+
warning: please see https://github.com/whitequark/parser#compatibility-with-ruby-mri.
59+
MSG
60+
end
61+
62+
it { should warn }
63+
end
3964
end
4065

4166
context 'and it reports an error' do
@@ -47,9 +72,21 @@
4772
end
4873

4974
it { should fail_hook }
75+
76+
context 'and it printed warnings to stderr' do
77+
before do
78+
result.stub(:stderr).and_return(normalize_indent(<<-MSG))
79+
warning: parser/current is loading parser/ruby21, which recognizes
80+
warning: 2.1.8-compliant syntax, but you are running 2.1.1.
81+
warning: please see https://github.com/whitequark/parser#compatibility-with-ruby-mri.
82+
MSG
83+
end
84+
85+
it { should fail_hook }
86+
end
5087
end
5188

52-
context 'when there is an error running rubocop' do
89+
context 'when a generic error message is written to stderr' do
5390
before do
5491
result.stub(:stdout).and_return('')
5592
result.stub(:stderr).and_return([

spec/overcommit/message_processor_spec.rb

+79-2
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
WMH = Overcommit::MessageProcessor::WARNINGS_MODIFIED_HEADER + "\n"
77
EUH = Overcommit::MessageProcessor::ERRORS_UNMODIFIED_HEADER + "\n"
88
WUH = Overcommit::MessageProcessor::WARNINGS_UNMODIFIED_HEADER + "\n"
9+
EGH = Overcommit::MessageProcessor::ERRORS_GENERIC_HEADER + "\n"
10+
WGH = Overcommit::MessageProcessor::WARNINGS_GENERIC_HEADER + "\n"
911

1012
let(:config) { double('config') }
1113
let(:context) { double('context') }
@@ -27,11 +29,11 @@
2729
end
2830
end
2931

30-
def error(file, line)
32+
def error(file = nil, line = nil)
3133
Overcommit::Hook::Message.new(:error, file, line, 'Error')
3234
end
3335

34-
def warning(file, line)
36+
def warning(file = nil, line = nil)
3537
Overcommit::Hook::Message.new(:warning, file, line, 'Warning')
3638
end
3739

@@ -209,5 +211,80 @@ def warning(file, line)
209211
it { should == [:fail, "#{EMH}Error\n#{WMH}Warning\n"] }
210212
end
211213
end
214+
215+
context 'when there are generic errors' do
216+
let(:messages) { [error] * 2 }
217+
let(:setting) { 'report' }
218+
219+
it { should == [:fail, "Error\nError\n"] }
220+
end
221+
222+
context 'when there are generic warnings' do
223+
let(:messages) { [warning] * 2 }
224+
let(:setting) { 'report' }
225+
226+
it { should == [:warn, "Warning\nWarning\n"] }
227+
end
228+
229+
context 'when there are generic errors and warnings' do
230+
let(:messages) { [warning, error] * 2 }
231+
let(:setting) { 'report' }
232+
233+
it { should == [:fail, "Warning\nError\nWarning\nError\n"] }
234+
end
235+
236+
context 'when there are errors and warnings on modified/unmodified lines' do
237+
let(:modified_lines) { { 'a.txt' => [2], 'b.txt' => [3, 4] } }
238+
let(:setting) { 'report' }
239+
240+
let(:messages) do
241+
[
242+
warning('a.txt', 3),
243+
warning('b.txt', 3),
244+
error('a.txt', 2),
245+
error('b.txt', 5),
246+
]
247+
end
248+
249+
context 'and there are generic errors before them' do
250+
let(:messages) { [error] * 2 + super() }
251+
252+
it do
253+
should == [:fail, "#{EGH}Error\nError\n" \
254+
"#{EMH}Error\n#{WMH}Warning\n" \
255+
"#{EUH}Error\n#{WUH}Warning\n"]
256+
end
257+
end
258+
259+
context 'and there are generic warnings before them' do
260+
let(:messages) { [warning] * 2 + super() }
261+
262+
it do
263+
should == [:fail, "#{WGH}Warning\nWarning\n" \
264+
"#{EMH}Error\n#{WMH}Warning\n" \
265+
"#{EUH}Error\n#{WUH}Warning\n"]
266+
end
267+
end
268+
269+
context 'and there are generic errors after them' do
270+
let(:messages) { super() + [error] * 2 }
271+
272+
it do
273+
should == [:fail, "#{EGH}Error\nError\n" \
274+
"#{EMH}Error\n#{WMH}Warning\n" \
275+
"#{EUH}Error\n#{WUH}Warning\n"]
276+
end
277+
end
278+
279+
context 'and there are generic warnings after them' do
280+
let(:messages) { super() + [warning] * 2 }
281+
282+
it do
283+
should == [:fail, "#{WGH}Warning\nWarning\n" \
284+
"#{EMH}Error\n#{WMH}Warning\n" \
285+
"#{EUH}Error\n#{WUH}Warning\n"]
286+
end
287+
end
288+
end
212289
end
213290
end

0 commit comments

Comments
 (0)