rubocop-rspec 1.37.0 → 1.40.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +52 -0
- data/README.md +2 -62
- data/config/default.yml +155 -15
- data/lib/rubocop-rspec.rb +2 -1
- data/lib/rubocop/cop/rspec/capybara/visibility_matcher.rb +69 -0
- data/lib/rubocop/cop/rspec/context_wording.rb +5 -1
- data/lib/rubocop/cop/rspec/cop.rb +9 -29
- data/lib/rubocop/cop/rspec/describe_class.rb +15 -2
- data/lib/rubocop/cop/rspec/describe_method.rb +0 -1
- data/lib/rubocop/cop/rspec/empty_hook.rb +50 -0
- data/lib/rubocop/cop/rspec/expect_actual.rb +27 -2
- data/lib/rubocop/cop/rspec/factory_bot/factory_class_name.rb +16 -1
- data/lib/rubocop/cop/rspec/file_path.rb +32 -4
- data/lib/rubocop/cop/rspec/hooks_before_examples.rb +3 -21
- data/lib/rubocop/cop/rspec/instance_variable.rb +13 -4
- data/lib/rubocop/cop/rspec/leading_subject.rb +3 -10
- data/lib/rubocop/cop/rspec/let_before_examples.rb +3 -21
- data/lib/rubocop/cop/rspec/message_chain.rb +1 -1
- data/lib/rubocop/cop/rspec/named_subject.rb +6 -6
- data/lib/rubocop/cop/rspec/rails/http_status.rb +2 -0
- data/lib/rubocop/cop/rspec/repeated_description.rb +17 -2
- data/lib/rubocop/cop/rspec/repeated_example_group_body.rb +97 -0
- data/lib/rubocop/cop/rspec/repeated_example_group_description.rb +96 -0
- data/lib/rubocop/cop/rspec/scattered_let.rb +13 -0
- data/lib/rubocop/cop/rspec/scattered_setup.rb +27 -9
- data/lib/rubocop/cop/rspec/shared_examples.rb +1 -0
- data/lib/rubocop/cop/rspec/subject_stub.rb +25 -47
- data/lib/rubocop/cop/rspec/variable_definition.rb +56 -0
- data/lib/rubocop/cop/rspec/variable_name.rb +47 -0
- data/lib/rubocop/cop/rspec_cops.rb +7 -1
- data/lib/rubocop/rspec/corrector/move_node.rb +52 -0
- data/lib/rubocop/rspec/description_extractor.rb +2 -6
- data/lib/rubocop/rspec/example.rb +1 -1
- data/lib/rubocop/rspec/hook.rb +44 -11
- data/lib/rubocop/rspec/language.rb +20 -0
- data/lib/rubocop/rspec/language/node_pattern.rb +1 -1
- data/lib/rubocop/rspec/variable.rb +16 -0
- data/lib/rubocop/rspec/version.rb +1 -1
- metadata +16 -9
- data/lib/rubocop/rspec/util.rb +0 -19
| @@ -58,6 +58,13 @@ module RuboCop | |
| 58 58 | 
             
                      (block (send (const nil? :Class) :new ...) ...)
         | 
| 59 59 | 
             
                    PATTERN
         | 
| 60 60 |  | 
| 61 | 
            +
                    def_node_matcher :custom_matcher?, <<-PATTERN
         | 
| 62 | 
            +
                      (block {
         | 
| 63 | 
            +
                        (send nil? :matcher sym)
         | 
| 64 | 
            +
                        (send (const (const nil? :RSpec) :Matchers) :define sym)
         | 
| 65 | 
            +
                      } ...)
         | 
| 66 | 
            +
                    PATTERN
         | 
| 67 | 
            +
             | 
| 61 68 | 
             
                    def_node_search :ivar_usage, '$(ivar $_)'
         | 
| 62 69 |  | 
| 63 70 | 
             
                    def_node_search :ivar_assigned?, '(ivasgn % ...)'
         | 
| @@ -66,8 +73,8 @@ module RuboCop | |
| 66 73 | 
             
                      return unless spec_group?(node)
         | 
| 67 74 |  | 
| 68 75 | 
             
                      ivar_usage(node) do |ivar, name|
         | 
| 69 | 
            -
                         | 
| 70 | 
            -
                         | 
| 76 | 
            +
                        next if valid_usage?(ivar)
         | 
| 77 | 
            +
                        next if assignment_only? && !ivar_assigned?(node, name)
         | 
| 71 78 |  | 
| 72 79 | 
             
                        add_offense(ivar)
         | 
| 73 80 | 
             
                      end
         | 
| @@ -75,8 +82,10 @@ module RuboCop | |
| 75 82 |  | 
| 76 83 | 
             
                    private
         | 
| 77 84 |  | 
| 78 | 
            -
                    def  | 
| 79 | 
            -
                      node.each_ancestor(:block).any?  | 
| 85 | 
            +
                    def valid_usage?(node)
         | 
| 86 | 
            +
                      node.each_ancestor(:block).any? do |block|
         | 
| 87 | 
            +
                        dynamic_class?(block) || custom_matcher?(block)
         | 
| 88 | 
            +
                      end
         | 
| 80 89 | 
             
                    end
         | 
| 81 90 |  | 
| 82 91 | 
             
                    def assignment_only?
         | 
| @@ -32,8 +32,6 @@ module RuboCop | |
| 32 32 | 
             
                  #     it { expect_something_else }
         | 
| 33 33 | 
             
                  #
         | 
| 34 34 | 
             
                  class LeadingSubject < Cop
         | 
| 35 | 
            -
                    include RangeHelp
         | 
| 36 | 
            -
             | 
| 37 35 | 
             
                    MSG = 'Declare `subject` above any other `%<offending>s` declarations.'
         | 
| 38 36 |  | 
| 39 37 | 
             
                    def on_block(node)
         | 
| @@ -58,10 +56,9 @@ module RuboCop | |
| 58 56 | 
             
                    def autocorrect(node)
         | 
| 59 57 | 
             
                      lambda do |corrector|
         | 
| 60 58 | 
             
                        first_node = find_first_offending_node(node)
         | 
| 61 | 
            -
                         | 
| 62 | 
            -
             | 
| 63 | 
            -
                         | 
| 64 | 
            -
                        corrector.remove(node_range(node))
         | 
| 59 | 
            +
                        RuboCop::RSpec::Corrector::MoveNode.new(
         | 
| 60 | 
            +
                          node, corrector, processed_source
         | 
| 61 | 
            +
                        ).move_before(first_node)
         | 
| 65 62 | 
             
                      end
         | 
| 66 63 | 
             
                    end
         | 
| 67 64 |  | 
| @@ -75,10 +72,6 @@ module RuboCop | |
| 75 72 | 
             
                      node.parent.children.find { |sibling| offending?(sibling) }
         | 
| 76 73 | 
             
                    end
         | 
| 77 74 |  | 
| 78 | 
            -
                    def node_range(node)
         | 
| 79 | 
            -
                      range_by_whole_lines(node.source_range, include_final_newline: true)
         | 
| 80 | 
            -
                    end
         | 
| 81 | 
            -
             | 
| 82 75 | 
             
                    def in_spec_block?(node)
         | 
| 83 76 | 
             
                      node.each_ancestor(:block).any? do |ancestor|
         | 
| 84 77 | 
             
                        example?(ancestor)
         | 
| @@ -31,9 +31,6 @@ module RuboCop | |
| 31 31 | 
             
                  #     expect(some).to be
         | 
| 32 32 | 
             
                  #   end
         | 
| 33 33 | 
             
                  class LetBeforeExamples < Cop
         | 
| 34 | 
            -
                    include RangeHelp
         | 
| 35 | 
            -
                    include RuboCop::RSpec::FinalEndLocation
         | 
| 36 | 
            -
             | 
| 37 34 | 
             
                    MSG = 'Move `let` before the examples in the group.'
         | 
| 38 35 |  | 
| 39 36 | 
             
                    def_node_matcher :example_or_group?, <<-PATTERN
         | 
| @@ -52,11 +49,9 @@ module RuboCop | |
| 52 49 | 
             
                    def autocorrect(node)
         | 
| 53 50 | 
             
                      lambda do |corrector|
         | 
| 54 51 | 
             
                        first_example = find_first_example(node.parent)
         | 
| 55 | 
            -
                         | 
| 56 | 
            -
             | 
| 57 | 
            -
             | 
| 58 | 
            -
                        corrector.insert_before(first_example_pos, source(node) + indent)
         | 
| 59 | 
            -
                        corrector.remove(node_range_with_surrounding_space(node))
         | 
| 52 | 
            +
                        RuboCop::RSpec::Corrector::MoveNode.new(
         | 
| 53 | 
            +
                          node, corrector, processed_source
         | 
| 54 | 
            +
                        ).move_before(first_example)
         | 
| 60 55 | 
             
                      end
         | 
| 61 56 | 
             
                    end
         | 
| 62 57 |  | 
| @@ -80,19 +75,6 @@ module RuboCop | |
| 80 75 | 
             
                    def find_first_example(node)
         | 
| 81 76 | 
             
                      node.children.find { |sibling| example_or_group?(sibling) }
         | 
| 82 77 | 
             
                    end
         | 
| 83 | 
            -
             | 
| 84 | 
            -
                    def node_range_with_surrounding_space(node)
         | 
| 85 | 
            -
                      range = node_range(node)
         | 
| 86 | 
            -
                      range_by_whole_lines(range, include_final_newline: true)
         | 
| 87 | 
            -
                    end
         | 
| 88 | 
            -
             | 
| 89 | 
            -
                    def source(node)
         | 
| 90 | 
            -
                      node_range(node).source
         | 
| 91 | 
            -
                    end
         | 
| 92 | 
            -
             | 
| 93 | 
            -
                    def node_range(node)
         | 
| 94 | 
            -
                      node.loc.expression.with(end_pos: final_end_location(node).end_pos)
         | 
| 95 | 
            -
                    end
         | 
| 96 78 | 
             
                  end
         | 
| 97 79 | 
             
                end
         | 
| 98 80 | 
             
              end
         | 
| @@ -6,11 +6,11 @@ module RuboCop | |
| 6 6 | 
             
                  # Checks for explicitly referenced test subjects.
         | 
| 7 7 | 
             
                  #
         | 
| 8 8 | 
             
                  # RSpec lets you declare an "implicit subject" using `subject { ... }`
         | 
| 9 | 
            -
                  # which allows for tests like `it {  | 
| 10 | 
            -
                  # reference your test subject you should explicitly | 
| 11 | 
            -
                  # `subject(:your_subject_name) { ... }`. Your test subjects | 
| 12 | 
            -
                  # the most important object in your tests so they deserve | 
| 13 | 
            -
                  # name.
         | 
| 9 | 
            +
                  # which allows for tests like `it { is_expected.to be_valid }`.
         | 
| 10 | 
            +
                  # If you need to reference your test subject you should explicitly
         | 
| 11 | 
            +
                  # name it using `subject(:your_subject_name) { ... }`. Your test subjects
         | 
| 12 | 
            +
                  # should be the most important object in your tests so they deserve
         | 
| 13 | 
            +
                  # a descriptive name.
         | 
| 14 14 | 
             
                  #
         | 
| 15 15 | 
             
                  # This cop can be configured in your configuration using the
         | 
| 16 16 | 
             
                  # `IgnoreSharedExamples` which will not report offenses for implicit
         | 
| @@ -39,7 +39,7 @@ module RuboCop | |
| 39 39 | 
             
                  #   RSpec.describe Foo do
         | 
| 40 40 | 
             
                  #     subject(:user) { described_class.new }
         | 
| 41 41 | 
             
                  #
         | 
| 42 | 
            -
                  #     it {  | 
| 42 | 
            +
                  #     it { is_expected.to be_valid }
         | 
| 43 43 | 
             
                  #   end
         | 
| 44 44 | 
             
                  class NamedSubject < Cop
         | 
| 45 45 | 
             
                    MSG = 'Name your test subject if you need '\
         | 
| @@ -70,6 +70,7 @@ module RuboCop | |
| 70 70 | 
             
                              'to describe HTTP status code.'
         | 
| 71 71 |  | 
| 72 72 | 
             
                        attr_reader :node
         | 
| 73 | 
            +
             | 
| 73 74 | 
             
                        def initialize(node)
         | 
| 74 75 | 
             
                          @node = node
         | 
| 75 76 | 
             
                        end
         | 
| @@ -110,6 +111,7 @@ module RuboCop | |
| 110 111 | 
             
                        ALLOWED_STATUSES = %i[error success missing redirect].freeze
         | 
| 111 112 |  | 
| 112 113 | 
             
                        attr_reader :node
         | 
| 114 | 
            +
             | 
| 113 115 | 
             
                        def initialize(node)
         | 
| 114 116 | 
             
                          @node = node
         | 
| 115 117 | 
             
                        end
         | 
| @@ -29,6 +29,17 @@ module RuboCop | |
| 29 29 | 
             
                  #       end
         | 
| 30 30 | 
             
                  #     end
         | 
| 31 31 | 
             
                  #
         | 
| 32 | 
            +
                  #     # good
         | 
| 33 | 
            +
                  #     RSpec.describe User do
         | 
| 34 | 
            +
                  #       it 'is valid' do
         | 
| 35 | 
            +
                  #         # ...
         | 
| 36 | 
            +
                  #       end
         | 
| 37 | 
            +
                  #
         | 
| 38 | 
            +
                  #       it 'is valid', :flag do
         | 
| 39 | 
            +
                  #         # ...
         | 
| 40 | 
            +
                  #       end
         | 
| 41 | 
            +
                  #     end
         | 
| 42 | 
            +
                  #
         | 
| 32 43 | 
             
                  class RepeatedDescription < Cop
         | 
| 33 44 | 
             
                    MSG = "Don't repeat descriptions within an example group."
         | 
| 34 45 |  | 
| @@ -47,14 +58,18 @@ module RuboCop | |
| 47 58 | 
             
                      grouped_examples =
         | 
| 48 59 | 
             
                        RuboCop::RSpec::ExampleGroup.new(node)
         | 
| 49 60 | 
             
                          .examples
         | 
| 50 | 
            -
                          .group_by( | 
| 61 | 
            +
                          .group_by { |example| example_signature(example) }
         | 
| 51 62 |  | 
| 52 63 | 
             
                      grouped_examples
         | 
| 53 | 
            -
                        .select { | | 
| 64 | 
            +
                        .select { |signatures, group| signatures.any? && group.size > 1 }
         | 
| 54 65 | 
             
                        .values
         | 
| 55 66 | 
             
                        .flatten
         | 
| 56 67 | 
             
                        .map(&:definition)
         | 
| 57 68 | 
             
                    end
         | 
| 69 | 
            +
             | 
| 70 | 
            +
                    def example_signature(example)
         | 
| 71 | 
            +
                      [example.metadata, example.doc_string]
         | 
| 72 | 
            +
                    end
         | 
| 58 73 | 
             
                  end
         | 
| 59 74 | 
             
                end
         | 
| 60 75 | 
             
              end
         | 
| @@ -0,0 +1,97 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module RuboCop
         | 
| 4 | 
            +
              module Cop
         | 
| 5 | 
            +
                module RSpec
         | 
| 6 | 
            +
                  # Check for repeated describe and context block body.
         | 
| 7 | 
            +
                  #
         | 
| 8 | 
            +
                  # @example
         | 
| 9 | 
            +
                  #
         | 
| 10 | 
            +
                  #    # bad
         | 
| 11 | 
            +
                  #    describe 'cool feature x' do
         | 
| 12 | 
            +
                  #      it { cool_predicate }
         | 
| 13 | 
            +
                  #    end
         | 
| 14 | 
            +
                  #
         | 
| 15 | 
            +
                  #    describe 'cool feature y' do
         | 
| 16 | 
            +
                  #      it { cool_predicate }
         | 
| 17 | 
            +
                  #    end
         | 
| 18 | 
            +
                  #
         | 
| 19 | 
            +
                  #    # good
         | 
| 20 | 
            +
                  #    describe 'cool feature' do
         | 
| 21 | 
            +
                  #      it { cool_predicate }
         | 
| 22 | 
            +
                  #    end
         | 
| 23 | 
            +
                  #
         | 
| 24 | 
            +
                  #    describe 'another cool feature' do
         | 
| 25 | 
            +
                  #      it { another_predicate }
         | 
| 26 | 
            +
                  #    end
         | 
| 27 | 
            +
                  #
         | 
| 28 | 
            +
                  #    # good
         | 
| 29 | 
            +
                  #    context 'when case x', :tag do
         | 
| 30 | 
            +
                  #      it { cool_predicate }
         | 
| 31 | 
            +
                  #    end
         | 
| 32 | 
            +
                  #
         | 
| 33 | 
            +
                  #    context 'when case y' do
         | 
| 34 | 
            +
                  #      it { cool_predicate }
         | 
| 35 | 
            +
                  #    end
         | 
| 36 | 
            +
                  #
         | 
| 37 | 
            +
                  #    # good
         | 
| 38 | 
            +
                  #    context Array do
         | 
| 39 | 
            +
                  #      it { is_expected.to respond_to :each }
         | 
| 40 | 
            +
                  #    end
         | 
| 41 | 
            +
                  #
         | 
| 42 | 
            +
                  #    context Hash do
         | 
| 43 | 
            +
                  #      it { is_expected.to respond_to :each }
         | 
| 44 | 
            +
                  #    end
         | 
| 45 | 
            +
                  #
         | 
| 46 | 
            +
                  class RepeatedExampleGroupBody < Cop
         | 
| 47 | 
            +
                    MSG = 'Repeated %<group>s block body on line(s) %<loc>s'
         | 
| 48 | 
            +
             | 
| 49 | 
            +
                    def_node_matcher :several_example_groups?, <<-PATTERN
         | 
| 50 | 
            +
                      (begin <#example_group_with_body? #example_group_with_body? ...>)
         | 
| 51 | 
            +
                    PATTERN
         | 
| 52 | 
            +
             | 
| 53 | 
            +
                    def_node_matcher :metadata, '(block (send _ _ _ $...) ...)'
         | 
| 54 | 
            +
                    def_node_matcher :body, '(block _ args $...)'
         | 
| 55 | 
            +
                    def_node_matcher :const_arg, '(block (send _ _ $const ...) ...)'
         | 
| 56 | 
            +
             | 
| 57 | 
            +
                    def_node_matcher :skip_or_pending?, <<-PATTERN
         | 
| 58 | 
            +
                      (block <(send nil? {:skip :pending}) ...>)
         | 
| 59 | 
            +
                    PATTERN
         | 
| 60 | 
            +
             | 
| 61 | 
            +
                    def on_begin(node)
         | 
| 62 | 
            +
                      return unless several_example_groups?(node)
         | 
| 63 | 
            +
             | 
| 64 | 
            +
                      repeated_group_bodies(node).each do |group, repeats|
         | 
| 65 | 
            +
                        add_offense(group, message: message(group, repeats))
         | 
| 66 | 
            +
                      end
         | 
| 67 | 
            +
                    end
         | 
| 68 | 
            +
             | 
| 69 | 
            +
                    private
         | 
| 70 | 
            +
             | 
| 71 | 
            +
                    def repeated_group_bodies(node)
         | 
| 72 | 
            +
                      node
         | 
| 73 | 
            +
                        .children
         | 
| 74 | 
            +
                        .select { |child| example_group_with_body?(child) }
         | 
| 75 | 
            +
                        .reject { |child| skip_or_pending?(child) }
         | 
| 76 | 
            +
                        .group_by { |group| signature_keys(group) }
         | 
| 77 | 
            +
                        .values
         | 
| 78 | 
            +
                        .reject(&:one?)
         | 
| 79 | 
            +
                        .flat_map { |groups| add_repeated_lines(groups) }
         | 
| 80 | 
            +
                    end
         | 
| 81 | 
            +
             | 
| 82 | 
            +
                    def add_repeated_lines(groups)
         | 
| 83 | 
            +
                      repeated_lines = groups.map(&:first_line)
         | 
| 84 | 
            +
                      groups.map { |group| [group, repeated_lines - [group.first_line]] }
         | 
| 85 | 
            +
                    end
         | 
| 86 | 
            +
             | 
| 87 | 
            +
                    def signature_keys(group)
         | 
| 88 | 
            +
                      [metadata(group), body(group), const_arg(group)]
         | 
| 89 | 
            +
                    end
         | 
| 90 | 
            +
             | 
| 91 | 
            +
                    def message(group, repeats)
         | 
| 92 | 
            +
                      format(MSG, group: group.method_name, loc: repeats)
         | 
| 93 | 
            +
                    end
         | 
| 94 | 
            +
                  end
         | 
| 95 | 
            +
                end
         | 
| 96 | 
            +
              end
         | 
| 97 | 
            +
            end
         | 
| @@ -0,0 +1,96 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module RuboCop
         | 
| 4 | 
            +
              module Cop
         | 
| 5 | 
            +
                module RSpec
         | 
| 6 | 
            +
                  # Check for repeated example group descriptions.
         | 
| 7 | 
            +
                  #
         | 
| 8 | 
            +
                  # @example
         | 
| 9 | 
            +
                  #
         | 
| 10 | 
            +
                  #    # bad
         | 
| 11 | 
            +
                  #    describe 'cool feature' do
         | 
| 12 | 
            +
                  #      # example group
         | 
| 13 | 
            +
                  #    end
         | 
| 14 | 
            +
                  #
         | 
| 15 | 
            +
                  #    describe 'cool feature' do
         | 
| 16 | 
            +
                  #      # example group
         | 
| 17 | 
            +
                  #    end
         | 
| 18 | 
            +
                  #
         | 
| 19 | 
            +
                  #    # bad
         | 
| 20 | 
            +
                  #    context 'when case x' do
         | 
| 21 | 
            +
                  #      # example group
         | 
| 22 | 
            +
                  #    end
         | 
| 23 | 
            +
                  #
         | 
| 24 | 
            +
                  #    describe 'when case x' do
         | 
| 25 | 
            +
                  #      # example group
         | 
| 26 | 
            +
                  #    end
         | 
| 27 | 
            +
                  #
         | 
| 28 | 
            +
                  #    # good
         | 
| 29 | 
            +
                  #    describe 'cool feature' do
         | 
| 30 | 
            +
                  #      # example group
         | 
| 31 | 
            +
                  #    end
         | 
| 32 | 
            +
                  #
         | 
| 33 | 
            +
                  #    describe 'another cool feature' do
         | 
| 34 | 
            +
                  #      # example group
         | 
| 35 | 
            +
                  #    end
         | 
| 36 | 
            +
                  #
         | 
| 37 | 
            +
                  #    # good
         | 
| 38 | 
            +
                  #    context 'when case x' do
         | 
| 39 | 
            +
                  #      # example group
         | 
| 40 | 
            +
                  #    end
         | 
| 41 | 
            +
                  #
         | 
| 42 | 
            +
                  #    context 'when another case' do
         | 
| 43 | 
            +
                  #      # example group
         | 
| 44 | 
            +
                  #    end
         | 
| 45 | 
            +
                  #
         | 
| 46 | 
            +
                  class RepeatedExampleGroupDescription < Cop
         | 
| 47 | 
            +
                    MSG = 'Repeated %<group>s block description on line(s) %<loc>s'
         | 
| 48 | 
            +
             | 
| 49 | 
            +
                    def_node_matcher :several_example_groups?, <<-PATTERN
         | 
| 50 | 
            +
                      (begin <#example_group? #example_group? ...>)
         | 
| 51 | 
            +
                    PATTERN
         | 
| 52 | 
            +
             | 
| 53 | 
            +
                    def_node_matcher :doc_string_and_metadata, <<-PATTERN
         | 
| 54 | 
            +
                      (block (send _ _ $_ $...) ...)
         | 
| 55 | 
            +
                    PATTERN
         | 
| 56 | 
            +
             | 
| 57 | 
            +
                    def_node_matcher :skip_or_pending?, <<-PATTERN
         | 
| 58 | 
            +
                      (block <(send nil? {:skip :pending}) ...>)
         | 
| 59 | 
            +
                    PATTERN
         | 
| 60 | 
            +
             | 
| 61 | 
            +
                    def_node_matcher :empty_description?, '(block (send _ _) ...)'
         | 
| 62 | 
            +
             | 
| 63 | 
            +
                    def on_begin(node)
         | 
| 64 | 
            +
                      return unless several_example_groups?(node)
         | 
| 65 | 
            +
             | 
| 66 | 
            +
                      repeated_group_descriptions(node).each do |group, repeats|
         | 
| 67 | 
            +
                        add_offense(group, message: message(group, repeats))
         | 
| 68 | 
            +
                      end
         | 
| 69 | 
            +
                    end
         | 
| 70 | 
            +
             | 
| 71 | 
            +
                    private
         | 
| 72 | 
            +
             | 
| 73 | 
            +
                    def repeated_group_descriptions(node)
         | 
| 74 | 
            +
                      node
         | 
| 75 | 
            +
                        .children
         | 
| 76 | 
            +
                        .select { |child| example_group?(child) }
         | 
| 77 | 
            +
                        .reject { |child| skip_or_pending?(child) }
         | 
| 78 | 
            +
                        .reject { |child| empty_description?(child) }
         | 
| 79 | 
            +
                        .group_by { |group| doc_string_and_metadata(group) }
         | 
| 80 | 
            +
                        .values
         | 
| 81 | 
            +
                        .reject(&:one?)
         | 
| 82 | 
            +
                        .flat_map { |groups| add_repeated_lines(groups) }
         | 
| 83 | 
            +
                    end
         | 
| 84 | 
            +
             | 
| 85 | 
            +
                    def add_repeated_lines(groups)
         | 
| 86 | 
            +
                      repeated_lines = groups.map(&:first_line)
         | 
| 87 | 
            +
                      groups.map { |group| [group, repeated_lines - [group.first_line]] }
         | 
| 88 | 
            +
                    end
         | 
| 89 | 
            +
             | 
| 90 | 
            +
                    def message(group, repeats)
         | 
| 91 | 
            +
                      format(MSG, group: group.method_name, loc: repeats)
         | 
| 92 | 
            +
                    end
         | 
| 93 | 
            +
                  end
         | 
| 94 | 
            +
                end
         | 
| 95 | 
            +
              end
         | 
| 96 | 
            +
            end
         | 
| @@ -35,6 +35,15 @@ module RuboCop | |
| 35 35 | 
             
                      check_let_declarations(node.body)
         | 
| 36 36 | 
             
                    end
         | 
| 37 37 |  | 
| 38 | 
            +
                    def autocorrect(node)
         | 
| 39 | 
            +
                      lambda do |corrector|
         | 
| 40 | 
            +
                        first_let = find_first_let(node.parent)
         | 
| 41 | 
            +
                        RuboCop::RSpec::Corrector::MoveNode.new(
         | 
| 42 | 
            +
                          node, corrector, processed_source
         | 
| 43 | 
            +
                        ).move_after(first_let)
         | 
| 44 | 
            +
                      end
         | 
| 45 | 
            +
                    end
         | 
| 46 | 
            +
             | 
| 38 47 | 
             
                    private
         | 
| 39 48 |  | 
| 40 49 | 
             
                    def check_let_declarations(body)
         | 
| @@ -47,6 +56,10 @@ module RuboCop | |
| 47 56 | 
             
                        add_offense(node)
         | 
| 48 57 | 
             
                      end
         | 
| 49 58 | 
             
                    end
         | 
| 59 | 
            +
             | 
| 60 | 
            +
                    def find_first_let(node)
         | 
| 61 | 
            +
                      node.children.find { |child| let?(child) }
         | 
| 62 | 
            +
                    end
         | 
| 50 63 | 
             
                  end
         | 
| 51 64 | 
             
                end
         | 
| 52 65 | 
             
              end
         | 
| @@ -23,25 +23,43 @@ module RuboCop | |
| 23 23 | 
             
                  #   end
         | 
| 24 24 | 
             
                  #
         | 
| 25 25 | 
             
                  class ScatteredSetup < Cop
         | 
| 26 | 
            -
                    MSG = 'Do not define multiple hooks in the same  | 
| 26 | 
            +
                    MSG = 'Do not define multiple `%<hook_name>s` hooks in the same '\
         | 
| 27 | 
            +
                          'example group (also defined on %<lines>s).'
         | 
| 27 28 |  | 
| 28 29 | 
             
                    def on_block(node)
         | 
| 29 30 | 
             
                      return unless example_group?(node)
         | 
| 30 31 |  | 
| 31 | 
            -
                       | 
| 32 | 
            -
                         | 
| 32 | 
            +
                      repeated_hooks(node).each do |occurrences|
         | 
| 33 | 
            +
                        lines = occurrences.map(&:first_line)
         | 
| 34 | 
            +
             | 
| 35 | 
            +
                        occurrences.each do |occurrence|
         | 
| 36 | 
            +
                          lines_except_current = lines - [occurrence.first_line]
         | 
| 37 | 
            +
                          message = format(MSG, hook_name: occurrences.first.method_name,
         | 
| 38 | 
            +
                                           lines: lines_msg(lines_except_current))
         | 
| 39 | 
            +
                          add_offense(occurrence, message: message)
         | 
| 40 | 
            +
                        end
         | 
| 33 41 | 
             
                      end
         | 
| 34 42 | 
             
                    end
         | 
| 35 43 |  | 
| 36 | 
            -
                    def  | 
| 37 | 
            -
                      RuboCop::RSpec::ExampleGroup.new(node)
         | 
| 44 | 
            +
                    def repeated_hooks(node)
         | 
| 45 | 
            +
                      hooks = RuboCop::RSpec::ExampleGroup.new(node)
         | 
| 38 46 | 
             
                        .hooks
         | 
| 39 | 
            -
                        .select | 
| 40 | 
            -
                        .group_by { |hook| [hook.name, hook.scope] }
         | 
| 47 | 
            +
                        .select(&:knowable_scope?)
         | 
| 48 | 
            +
                        .group_by { |hook| [hook.name, hook.scope, hook.metadata] }
         | 
| 41 49 | 
             
                        .values
         | 
| 42 50 | 
             
                        .reject(&:one?)
         | 
| 43 | 
            -
             | 
| 44 | 
            -
             | 
| 51 | 
            +
             | 
| 52 | 
            +
                      hooks.map do |hook|
         | 
| 53 | 
            +
                        hook.map(&:to_node)
         | 
| 54 | 
            +
                      end
         | 
| 55 | 
            +
                    end
         | 
| 56 | 
            +
             | 
| 57 | 
            +
                    def lines_msg(numbers)
         | 
| 58 | 
            +
                      if numbers.size == 1
         | 
| 59 | 
            +
                        "line #{numbers.first}"
         | 
| 60 | 
            +
                      else
         | 
| 61 | 
            +
                        "lines #{numbers.join(', ')}"
         | 
| 62 | 
            +
                      end
         | 
| 45 63 | 
             
                    end
         | 
| 46 64 | 
             
                  end
         | 
| 47 65 | 
             
                end
         |