Skip to content

Commit

Permalink
Merge pull request #1854 from rubocop/empty-output
Browse files Browse the repository at this point in the history
Add new RSpec/EmptyOutput cop
  • Loading branch information
ydah authored Apr 4, 2024
2 parents eabbdc8 + cb3ca9c commit 5113a9b
Show file tree
Hide file tree
Showing 8 changed files with 156 additions and 0 deletions.
2 changes: 2 additions & 0 deletions .rubocop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,8 @@ RSpec/DuplicatedMetadata:
Enabled: true
RSpec/EmptyMetadata:
Enabled: true
RSpec/EmptyOutput:
Enabled: true
RSpec/Eq:
Enabled: true
RSpec/ExcessiveDocstringSpacing:
Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

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

## 2.28.0 (2024-03-30)

Expand Down
6 changes: 6 additions & 0 deletions config/default.yml
Original file line number Diff line number Diff line change
Expand Up @@ -366,6 +366,12 @@ RSpec/EmptyMetadata:
VersionAdded: '2.24'
Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/EmptyMetadata

RSpec/EmptyOutput:
Description: Check that the `output` matcher is not called with an empty string.
Enabled: pending
VersionAdded: "<<next>>"
Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/EmptyOutput

RSpec/Eq:
Description: Use `eq` instead of `be ==` to compare objects.
Enabled: pending
Expand Down
1 change: 1 addition & 0 deletions docs/modules/ROOT/pages/cops.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
* xref:cops_rspec.adoc#rspecemptylineafterhook[RSpec/EmptyLineAfterHook]
* xref:cops_rspec.adoc#rspecemptylineaftersubject[RSpec/EmptyLineAfterSubject]
* xref:cops_rspec.adoc#rspecemptymetadata[RSpec/EmptyMetadata]
* xref:cops_rspec.adoc#rspecemptyoutput[RSpec/EmptyOutput]
* xref:cops_rspec.adoc#rspeceq[RSpec/Eq]
* xref:cops_rspec.adoc#rspecexamplelength[RSpec/ExampleLength]
* xref:cops_rspec.adoc#rspecexamplewithoutdescription[RSpec/ExampleWithoutDescription]
Expand Down
31 changes: 31 additions & 0 deletions docs/modules/ROOT/pages/cops_rspec.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -1521,6 +1521,37 @@ describe 'Something'
* https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/EmptyMetadata
== RSpec/EmptyOutput
|===
| Enabled by default | Safe | Supports autocorrection | Version Added | Version Changed
| Pending
| Yes
| Always
| <<next>>
| -
|===
Check that the `output` matcher is not called with an empty string.
=== Examples
[source,ruby]
----
# bad
expect { foo }.to output('').to_stdout
expect { bar }.not_to output('').to_stderr
# good
expect { foo }.not_to output.to_stdout
expect { bar }.to output.to_stderr
----
=== References
* https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/EmptyOutput
== RSpec/Eq
|===
Expand Down
47 changes: 47 additions & 0 deletions lib/rubocop/cop/rspec/empty_output.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# frozen_string_literal: true

module RuboCop
module Cop
module RSpec
# Check that the `output` matcher is not called with an empty string.
#
# @example
# # bad
# expect { foo }.to output('').to_stdout
# expect { bar }.not_to output('').to_stderr
#
# # good
# expect { foo }.not_to output.to_stdout
# expect { bar }.to output.to_stderr
#
class EmptyOutput < Base
extend AutoCorrector

MSG = 'Use `%<runner>s` instead of matching on an empty output.'
RESTRICT_ON_SEND = Runners.all

# @!method matching_empty_output(node)
def_node_matcher :matching_empty_output, <<~PATTERN
(send
(block
(send nil? :expect) ...
)
#Runners.all
(send $(send nil? :output (str empty?)) ...)
)
PATTERN

def on_send(send_node)
matching_empty_output(send_node) do |node|
runner = send_node.method?(:to) ? 'not_to' : 'to'
message = format(MSG, runner: runner)
add_offense(node, message: message) do |corrector|
corrector.replace(send_node.loc.selector, runner)
corrector.replace(node, 'output')
end
end
end
end
end
end
end
1 change: 1 addition & 0 deletions lib/rubocop/cop/rspec_cops.rb
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@
require_relative 'rspec/empty_line_after_hook'
require_relative 'rspec/empty_line_after_subject'
require_relative 'rspec/empty_metadata'
require_relative 'rspec/empty_output'
require_relative 'rspec/eq'
require_relative 'rspec/example_length'
require_relative 'rspec/example_without_description'
Expand Down
67 changes: 67 additions & 0 deletions spec/rubocop/cop/rspec/empty_output_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# frozen_string_literal: true

RSpec.describe RuboCop::Cop::RSpec::EmptyOutput, :config do
it 'registers an offense when using `#output` with an empty string' do
expect_offense(<<~RUBY)
expect { foo }.to output('').to_stderr
^^^^^^^^^^ Use `not_to` instead of matching on an empty output.
expect { foo }.to output('').to_stdout
^^^^^^^^^^ Use `not_to` instead of matching on an empty output.
RUBY

expect_correction(<<~RUBY)
expect { foo }.not_to output.to_stderr
expect { foo }.not_to output.to_stdout
RUBY
end

it 'registers an offense when negatively matching `#output` with ' \
'an empty string' do
expect_offense(<<~RUBY)
expect { foo }.not_to output('').to_stderr
^^^^^^^^^^ Use `to` instead of matching on an empty output.
expect { foo }.to_not output('').to_stdout
^^^^^^^^^^ Use `to` instead of matching on an empty output.
RUBY

expect_correction(<<~RUBY)
expect { foo }.to output.to_stderr
expect { foo }.to output.to_stdout
RUBY
end

describe 'compound expectations' do
it 'does not register an offense when matching empty strings' do
expect_no_offenses(<<~RUBY)
expect {
:noop
}.to output('').to_stdout.and output('').to_stderr
RUBY
end

it 'does not register an offense when matching non-empty strings' do
expect_no_offenses(<<~RUBY)
expect {
warn "foo"
puts "bar"
}.to output("bar\n").to_stdout.and output(/foo/).to_stderr
RUBY
end
end

it 'does not register an offense when using `#output` with ' \
'a non-empty string' do
expect_no_offenses(<<~RUBY)
expect { foo }.to output('foo').to_stderr
expect { foo }.not_to output('foo').to_stderr
expect { foo }.to_not output('foo').to_stderr
RUBY
end

it 'does not register an offense when using `not_to output`' do
expect_no_offenses(<<~RUBY)
expect { foo }.not_to output.to_stderr
expect { foo }.to_not output.to_stderr
RUBY
end
end

0 comments on commit 5113a9b

Please sign in to comment.