Skip to content

Commit

Permalink
Add RepeatedDescription cop
Browse files Browse the repository at this point in the history
  • Loading branch information
backus committed Dec 26, 2016
1 parent ccd9402 commit 86daf54
Show file tree
Hide file tree
Showing 4 changed files with 108 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/RepeatedDescription:
Enabled: true
Description: Check for repeated description strings in example groups.

RSpec/SingleArgumentMessageChain:
Description: Checks that chains of messages contain more than one element.
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,6 +40,7 @@
require 'rubocop/cop/rspec/named_subject'
require 'rubocop/cop/rspec/nested_groups'
require 'rubocop/cop/rspec/not_to_not'
require 'rubocop/cop/rspec/repeated_description'
require 'rubocop/cop/rspec/single_argument_message_chain'
require 'rubocop/cop/rspec/subject_stub'
require 'rubocop/cop/rspec/verified_doubles'
84 changes: 84 additions & 0 deletions lib/rubocop/cop/rspec/repeated_description.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
module RuboCop
module Cop
module RSpec
# Check for repeated description strings in example groups.
#
# @example
#
# # bad
# RSpec.describe User do
# it 'is valid' do
# # ...
# end
#
# it 'is valid' do
# # ...
# end
# end
#
# # good
# RSpec.describe User do
# it 'is valid when first and last name are present' do
# # ...
# end
#
# it 'is valid when last name only is present' do
# # ...
# end
# end
#
class RepeatedDescription < Cop
include RuboCop::Cop::ConfigurableEnforcedStyle

def_node_matcher :example, <<-PATTERN
(block $(send _ {#{Examples::ALL.to_node_pattern}} ...) ...)
PATTERN

# Selectors which indicate that indicate we should stop searching
SCOPE_CHANGES = ExampleGroups::ALL + SharedGroups::ALL

# @!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

MSG = 'Repeated description'.freeze

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

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

private

def repeated_descriptions(node)
hooks_in_group(node)
.group_by(&:method_args)
.reject { |args, _| args.empty? }
.values
.reject(&:one?)
.flatten
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)

example(node, &block)
hooks_in_group(node, &block)
end
end
end
end
end
19 changes: 19 additions & 0 deletions spec/rubocop/cop/rspec/repeated_description_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# frozen_string_literal: true

describe RuboCop::Cop::RSpec::RepeatedDescription do
subject(:cop) { described_class.new }

it 'registers an offense for repeated descriptions' do
expect_violation(<<-RUBY)
describe 'doing x' do
it "does x" do
^^^^^^^^^^^ Repeated description
end
it "does x" do
^^^^^^^^^^^ Repeated description
end
end
RUBY
end
end

0 comments on commit 86daf54

Please sign in to comment.