lib/rubocop/cop/rspec/empty_example_group.rb in rubocop-rspec-1.42.0 vs lib/rubocop/cop/rspec/empty_example_group.rb in rubocop-rspec-1.43.0

- old
+ new

@@ -55,23 +55,107 @@ # # include_tests 'shared tests' # end # end # - class EmptyExampleGroup < Cop + class EmptyExampleGroup < Base MSG = 'Empty example group detected.' - def_node_search :contains_example?, <<-PATTERN + # @!method example_group_body(node) + # Match example group blocks and yield their body + # + # @example source that matches + # describe 'example group' do + # it { is_expected.to be } + # end + # + # @param node [RuboCop::AST::Node] + # @yield [RuboCop::AST::Node] example group body + def_node_matcher :example_group_body, <<~PATTERN + (block #{ExampleGroups::ALL.send_pattern} args $_) + PATTERN + + # @!method example_or_group_or_include?(node) + # Match examples, example groups and includes + # + # @example source that matches + # it { is_expected.to fly } + # describe('non-empty example groups too') { } + # it_behaves_like 'an animal' + # it_behaves_like('a cat') { let(:food) { 'milk' } } + # it_has_root_access + # + # @param node [RuboCop::AST::Node] + # @return [Array<RuboCop::AST::Node>] matching nodes + def_node_matcher :example_or_group_or_include?, <<~PATTERN { - #{(Examples::ALL + Includes::ALL).send_pattern} - (send _ #custom_include? ...) + #{Examples::ALL.block_pattern} + #{ExampleGroups::ALL.block_pattern} + #{Includes::ALL.send_pattern} + #{Includes::ALL.block_pattern} + (send nil? #custom_include? ...) } PATTERN - def on_block(node) - return unless example_group?(node) && !contains_example?(node) + # @!method examples_inside_block?(node) + # Match examples defined inside a block which is not a hook + # + # @example source that matches + # %w(r g b).each do |color| + # it { is_expected.to have_color(color) } + # end + # + # @example source that does not match + # before do + # it { is_expected.to fall_into_oblivion } + # end + # + # @param node [RuboCop::AST::Node] + # @return [Array<RuboCop::AST::Node>] matching nodes + def_node_matcher :examples_inside_block?, <<~PATTERN + (block !#{Hooks::ALL.send_pattern} _ #examples?) + PATTERN - add_offense(node.send_node) + # @!method examples_directly_or_in_block?(node) + # Match examples or examples inside blocks + # + # @example source that matches + # it { expect(drink).to be_cold } + # context('when winter') { it { expect(drink).to be_hot } } + # (1..5).each { |divisor| it { is_expected.to divide_by(divisor) } } + # + # @param node [RuboCop::AST::Node] + # @return [Array<RuboCop::AST::Node>] matching nodes + def_node_matcher :examples_directly_or_in_block?, <<~PATTERN + { + #example_or_group_or_include? + #examples_inside_block? + } + PATTERN + + # @!method examples?(node) + # Matches examples defined in scopes where they could run + # + # @example source that matches + # it { expect(myself).to be_run } + # describe { it { i_run_as_well } } + # + # @example source that does not match + # before { it { whatever here wont run anyway } } + # + # @param node [RuboCop::AST::Node] + # @return [Array<RuboCop::AST::Node>] matching nodes + def_node_matcher :examples?, <<~PATTERN + { + #examples_directly_or_in_block? + (begin <#examples_directly_or_in_block? ...>) + } + PATTERN + + def on_block(node) + example_group_body(node) do |body| + add_offense(node.send_node) unless examples?(body) + end end private def custom_include?(method_name)