Skip to content

Commit 5113a9b

Browse files
authored
Merge pull request #1854 from rubocop/empty-output
Add new RSpec/EmptyOutput cop
2 parents eabbdc8 + cb3ca9c commit 5113a9b

File tree

8 files changed

+156
-0
lines changed

8 files changed

+156
-0
lines changed

.rubocop.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,8 @@ RSpec/DuplicatedMetadata:
164164
Enabled: true
165165
RSpec/EmptyMetadata:
166166
Enabled: true
167+
RSpec/EmptyOutput:
168+
Enabled: true
167169
RSpec/Eq:
168170
Enabled: true
169171
RSpec/ExcessiveDocstringSpacing:

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
- Fix an autocorrect error for `RSpec/ExpectActual`. ([@bquorning])
66
- Add new `RSpec/UndescriptiveLiteralsDescription` cop. ([@ydah])
7+
- Add new `RSpec/EmptyOutput` cop. ([@bquorning])
78

89
## 2.28.0 (2024-03-30)
910

config/default.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -366,6 +366,12 @@ RSpec/EmptyMetadata:
366366
VersionAdded: '2.24'
367367
Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/EmptyMetadata
368368

369+
RSpec/EmptyOutput:
370+
Description: Check that the `output` matcher is not called with an empty string.
371+
Enabled: pending
372+
VersionAdded: "<<next>>"
373+
Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/EmptyOutput
374+
369375
RSpec/Eq:
370376
Description: Use `eq` instead of `be ==` to compare objects.
371377
Enabled: pending

docs/modules/ROOT/pages/cops.adoc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
* xref:cops_rspec.adoc#rspecemptylineafterhook[RSpec/EmptyLineAfterHook]
3333
* xref:cops_rspec.adoc#rspecemptylineaftersubject[RSpec/EmptyLineAfterSubject]
3434
* xref:cops_rspec.adoc#rspecemptymetadata[RSpec/EmptyMetadata]
35+
* xref:cops_rspec.adoc#rspecemptyoutput[RSpec/EmptyOutput]
3536
* xref:cops_rspec.adoc#rspeceq[RSpec/Eq]
3637
* xref:cops_rspec.adoc#rspecexamplelength[RSpec/ExampleLength]
3738
* xref:cops_rspec.adoc#rspecexamplewithoutdescription[RSpec/ExampleWithoutDescription]

docs/modules/ROOT/pages/cops_rspec.adoc

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1521,6 +1521,37 @@ describe 'Something'
15211521
15221522
* https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/EmptyMetadata
15231523
1524+
== RSpec/EmptyOutput
1525+
1526+
|===
1527+
| Enabled by default | Safe | Supports autocorrection | Version Added | Version Changed
1528+
1529+
| Pending
1530+
| Yes
1531+
| Always
1532+
| <<next>>
1533+
| -
1534+
|===
1535+
1536+
Check that the `output` matcher is not called with an empty string.
1537+
1538+
=== Examples
1539+
1540+
[source,ruby]
1541+
----
1542+
# bad
1543+
expect { foo }.to output('').to_stdout
1544+
expect { bar }.not_to output('').to_stderr
1545+
1546+
# good
1547+
expect { foo }.not_to output.to_stdout
1548+
expect { bar }.to output.to_stderr
1549+
----
1550+
1551+
=== References
1552+
1553+
* https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/EmptyOutput
1554+
15241555
== RSpec/Eq
15251556
15261557
|===
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
# frozen_string_literal: true
2+
3+
module RuboCop
4+
module Cop
5+
module RSpec
6+
# Check that the `output` matcher is not called with an empty string.
7+
#
8+
# @example
9+
# # bad
10+
# expect { foo }.to output('').to_stdout
11+
# expect { bar }.not_to output('').to_stderr
12+
#
13+
# # good
14+
# expect { foo }.not_to output.to_stdout
15+
# expect { bar }.to output.to_stderr
16+
#
17+
class EmptyOutput < Base
18+
extend AutoCorrector
19+
20+
MSG = 'Use `%<runner>s` instead of matching on an empty output.'
21+
RESTRICT_ON_SEND = Runners.all
22+
23+
# @!method matching_empty_output(node)
24+
def_node_matcher :matching_empty_output, <<~PATTERN
25+
(send
26+
(block
27+
(send nil? :expect) ...
28+
)
29+
#Runners.all
30+
(send $(send nil? :output (str empty?)) ...)
31+
)
32+
PATTERN
33+
34+
def on_send(send_node)
35+
matching_empty_output(send_node) do |node|
36+
runner = send_node.method?(:to) ? 'not_to' : 'to'
37+
message = format(MSG, runner: runner)
38+
add_offense(node, message: message) do |corrector|
39+
corrector.replace(send_node.loc.selector, runner)
40+
corrector.replace(node, 'output')
41+
end
42+
end
43+
end
44+
end
45+
end
46+
end
47+
end

lib/rubocop/cop/rspec_cops.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@
5454
require_relative 'rspec/empty_line_after_hook'
5555
require_relative 'rspec/empty_line_after_subject'
5656
require_relative 'rspec/empty_metadata'
57+
require_relative 'rspec/empty_output'
5758
require_relative 'rspec/eq'
5859
require_relative 'rspec/example_length'
5960
require_relative 'rspec/example_without_description'
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
# frozen_string_literal: true
2+
3+
RSpec.describe RuboCop::Cop::RSpec::EmptyOutput, :config do
4+
it 'registers an offense when using `#output` with an empty string' do
5+
expect_offense(<<~RUBY)
6+
expect { foo }.to output('').to_stderr
7+
^^^^^^^^^^ Use `not_to` instead of matching on an empty output.
8+
expect { foo }.to output('').to_stdout
9+
^^^^^^^^^^ Use `not_to` instead of matching on an empty output.
10+
RUBY
11+
12+
expect_correction(<<~RUBY)
13+
expect { foo }.not_to output.to_stderr
14+
expect { foo }.not_to output.to_stdout
15+
RUBY
16+
end
17+
18+
it 'registers an offense when negatively matching `#output` with ' \
19+
'an empty string' do
20+
expect_offense(<<~RUBY)
21+
expect { foo }.not_to output('').to_stderr
22+
^^^^^^^^^^ Use `to` instead of matching on an empty output.
23+
expect { foo }.to_not output('').to_stdout
24+
^^^^^^^^^^ Use `to` instead of matching on an empty output.
25+
RUBY
26+
27+
expect_correction(<<~RUBY)
28+
expect { foo }.to output.to_stderr
29+
expect { foo }.to output.to_stdout
30+
RUBY
31+
end
32+
33+
describe 'compound expectations' do
34+
it 'does not register an offense when matching empty strings' do
35+
expect_no_offenses(<<~RUBY)
36+
expect {
37+
:noop
38+
}.to output('').to_stdout.and output('').to_stderr
39+
RUBY
40+
end
41+
42+
it 'does not register an offense when matching non-empty strings' do
43+
expect_no_offenses(<<~RUBY)
44+
expect {
45+
warn "foo"
46+
puts "bar"
47+
}.to output("bar\n").to_stdout.and output(/foo/).to_stderr
48+
RUBY
49+
end
50+
end
51+
52+
it 'does not register an offense when using `#output` with ' \
53+
'a non-empty string' do
54+
expect_no_offenses(<<~RUBY)
55+
expect { foo }.to output('foo').to_stderr
56+
expect { foo }.not_to output('foo').to_stderr
57+
expect { foo }.to_not output('foo').to_stderr
58+
RUBY
59+
end
60+
61+
it 'does not register an offense when using `not_to output`' do
62+
expect_no_offenses(<<~RUBY)
63+
expect { foo }.not_to output.to_stderr
64+
expect { foo }.to_not output.to_stderr
65+
RUBY
66+
end
67+
end

0 commit comments

Comments
 (0)