lib/test_prof/cops/rspec/aggregate_failures.rb
# frozen_string_literal: true require 'rubocop' module RuboCop module Cop module RSpec # Rejects and auto-corrects the usage of one-liners examples in favour of # :aggregate_failures feature. # # Example: # # # bad # it { is_expected.to be_success } # it { is_expected.to have_header('X-TOTAL-PAGES', 10) } # it { is_expected.to have_header('X-NEXT-PAGE', 2) } # # # good # it "returns the second page", :aggregate_failures do # is_expected.to be_success # is_expected.to have_header('X-TOTAL-PAGES', 10) # is_expected.to have_header('X-NEXT-PAGE', 2) # end # class AggregateFailures < RuboCop::Cop::Cop # From https://github.com/backus/rubocop-rspec/blob/master/lib/rubocop/rspec/language.rb GROUP_BLOCKS = %i[ describe context feature example_group xdescribe xcontext xfeature fdescribe fcontext ffeature ].freeze EXAMPLE_BLOCKS = %i[ it specify example scenario its fit fspecify fexample fscenario focus xit xspecify xexample xscenario ski pending ].freeze def on_block(node) method, _args, body = *node return unless body&.begin_type? _receiver, method_name, _object = *method return unless GROUP_BLOCKS.include?(method_name) return if check_node(body) add_offense( node, :expression, 'Use :aggregate_failures instead of several one-liners.' ) end def autocorrect(node) _method, _args, body = *node iter = body.children.each first_example = loop do child = iter.next break child if oneliner?(child) end base_indent = " " * first_example.source_range.column replacements = [ header_from(first_example), body_from(first_example, base_indent) ] last_example = nil loop do child = iter.next break unless oneliner?(child) last_example = child replacements << body_from(child, base_indent) end replacements << "#{base_indent}end" range = first_example.source_range.begin.join( last_example.source_range.end ) replacement = replacements.join("\n") lambda do |corrector| corrector.replace(range, replacement) end end private def check_node(node) offenders = 0 node.children.each do |child| if oneliner?(child) offenders += 1 elsif example_node?(child) break if offenders > 1 offenders = 0 end end offenders < 2 end def oneliner?(node) node&.block_type? && (node.source.lines.size == 1) && example_node?(node) end def example_node?(node) method, _args, _body = *node _receiver, method_name, _object = *method EXAMPLE_BLOCKS.include?(method_name) end def header_from(node) method, _args, _body = *node _receiver, method_name, _object = *method %(#{method_name} "works", :aggregate_failures do) end def body_from(node, base_indent = '') _method, _args, body = *node "#{base_indent}#{indent}#{body.source}" end def indent @indent ||= " " * (config.for_cop('IndentationWidth')['Width'] || 2) end end end end end