Skip to content

Commit

Permalink
Add ScatteredSetup cop
Browse files Browse the repository at this point in the history
  • Loading branch information
backus committed Dec 20, 2016
1 parent 2e6e813 commit b24343d
Show file tree
Hide file tree
Showing 4 changed files with 146 additions and 0 deletions.
4 changes: 4 additions & 0 deletions config/default.yml
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,10 @@ RSpec/NotToNot:
- to_not
Enabled: true

RSpec/ScatteredSetup:
Description: Checks for separated hooks in the same example group.
Enabled: true

RSpec/SubjectStub:
Description: Checks for stubbed test subjects.
Enabled: true
Expand Down
1 change: 1 addition & 0 deletions lib/rubocop-rspec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -40,5 +40,6 @@
require 'rubocop/cop/rspec/named_subject'
require 'rubocop/cop/rspec/nested_groups'
require 'rubocop/cop/rspec/not_to_not'
require 'rubocop/cop/rspec/scattered_setup'
require 'rubocop/cop/rspec/subject_stub'
require 'rubocop/cop/rspec/verified_doubles'
79 changes: 79 additions & 0 deletions lib/rubocop/cop/rspec/scattered_setup.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
# frozen_string_literal: true

module RuboCop
module Cop
module RSpec
# Checks for separated hooks in the same example group.
#
# Unify `before`, `after`, and `around` hooks when possible.
#
# @example
# # bad
# describe Foo do
# before { setup1 }
# before { setup2 }
# end
#
# # good
# describe Foo do
# before do
# setup1
# setup2
# end
# end
#
class ScatteredSetup < Cop
include RuboCop::RSpec::TopLevelDescribe

MSG = 'Do not define multiple hooks in the same example group.'.freeze

# Selectors which indicate that indicate we should stop searching
# for more hook definitions
SCOPE_CHANGES = ExampleGroups::ALL + SharedGroups::ALL + Examples::ALL

# @!method hook(node)
#
# Detect if node is `before`, `after`, `around`
def_node_matcher :hook, <<-PATTERN
(block $(send nil {#{Hooks::ALL.to_node_pattern}} ...) ...)
PATTERN

# @!method scope_change?(node)
#
# Detect if the node is an example or a new example group
#
def_node_matcher :scope_change?, <<-PATTERN
(block (send _ {#{SCOPE_CHANGES.to_node_pattern}} ...) ...)
PATTERN

def on_block(node)
return unless example_group?(node)

repeated =
hooks_in_group(node)
.group_by(&:method_name)
.values
.reject(&:one?)
.flatten

repeated.each do |repeated_hook|
add_offense(repeated_hook, :expression)
end
end

def hooks_in_group(node, &block)
return to_enum(__method__, node) unless block_given?

node.each_child_node { |child| find_hooks(child, &block) }
end

def find_hooks(node, &block)
return if scope_change?(node)

hook(node, &block)
hooks_in_group(node, &block)
end
end
end
end
end
62 changes: 62 additions & 0 deletions spec/rubocop/cop/rspec/scattered_setup_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
describe RuboCop::Cop::RSpec::ScatteredSetup do
subject(:cop) { described_class.new }

it 'flags multiple hooks in the same example group' do
expect_violation(<<-RUBY)
describe Foo do
before { bar }
^^^^^^ Do not define multiple hooks in the same example group.
before { baz }
^^^^^^ Do not define multiple hooks in the same example group.
end
RUBY
end

it 'does not flag different hooks' do
expect_no_violations(<<-RUBY)
describe Foo do
before { bar }
after { baz }
around { qux }
end
RUBY
end

it 'does not flag hooks in different example groups' do
expect_no_violations(<<-RUBY)
describe Foo do
before { bar }
describe '.baz' do
before { baz }
end
end
RUBY
end

it 'does not flag hooks in different shared contexts' do
expect_no_violations(<<-RUBY)
describe Foo do
shared_context 'one' do
before { bar }
end
shared_context 'two' do
before { baz }
end
end
RUBY
end

it 'does not flag similar method names inside of examples' do
expect_no_violations(<<-RUBY)
describe Foo do
before { bar }
it 'uses an instance method called before' do
expect(before { tricky }).to_not confuse_rubocop_rspec
end
end
RUBY
end
end

0 comments on commit b24343d

Please sign in to comment.