Created
March 28, 2017 16:54
-
-
Save palkan/ac4eb92f08d0b33f41a9330445e76fbd to your computer and use it in GitHub Desktop.
Rubocop: RSpec/AggregateFailures
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
require 'rubocop/rspec/language' | |
module RuboCop | |
module Cop | |
module RSpec | |
class AggregateFailures < RuboCop::Cop::Cop | |
GROUP_BLOCKS = RuboCop::RSpec::Language::ExampleGroups::ALL | |
EXAMPLE_BLOCKS = RuboCop::RSpec::Language::Examples::ALL | |
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) | |
unless check_node(body) | |
add_offense( | |
node, | |
:expression, | |
'Use :aggregate_failures instead of several one-liners.' | |
) | |
end | |
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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
require 'rubocop' | |
require 'rubocop/rspec/support' | |
require 'rubocop/cop/rspec/aggregate_failures' | |
describe RuboCop::Cop::RSpec::AggregateFailures, :config do | |
subject(:cop) { described_class.new(config) } | |
it 'rejects two one-liners in a row' do | |
inspect_source(cop, ['context "request" do', | |
' it { is_expected.to be_success }', | |
' it { expect(response.body).to eq "OK" }', | |
'end']) | |
expect(cop.offenses.size).to eq(1) | |
expect(cop.messages.first).to eq('Use :aggregate_failures instead of several one-liners.') | |
end | |
it 'rejects two one-liners when blank lines and non-example blocks' do | |
inspect_source(cop, ['context "request" do', | |
' let(:user) { create(:user) } ', | |
' before { get "/" }', | |
'', | |
' it { is_expected.to be_success }', | |
' ', | |
' ', | |
' it { expect(response.body).to eq "OK" }', | |
'end']) | |
expect(cop.offenses.size).to eq(1) | |
expect(cop.messages.first).to eq('Use :aggregate_failures instead of several one-liners.') | |
end | |
it 'rejects one-liners with nested context' do | |
inspect_source(cop, ['context "request" do', | |
' it { is_expected.to be_success }', | |
' it "works" do', | |
' expect(subject).to be_ok', | |
' end', | |
' it { expect(response.body).to eq "OK" }', | |
'', | |
' context "sub-request" do', | |
' let(:params) { "?q=1" }', | |
' it { is_expected.to be_valid }', | |
' end', | |
'end']) | |
expect(cop.offenses).to be_empty | |
end | |
it 'accepts single one-liner' do | |
inspect_source(cop, ['context "request" do', | |
' it { is_expected.to be_success }', | |
'end']) | |
expect(cop.offenses).to be_empty | |
end | |
it 'accepts one-liners separated by multiliners' do | |
inspect_source(cop, ['context "request" do', | |
' it { is_expected.to be_success }', | |
' it "works" do', | |
' expect(subject).to be_ok', | |
' end', | |
' it { expect(response.body).to eq "OK" }', | |
'end']) | |
expect(cop.offenses).to be_empty | |
end | |
it 'handles edge cases' do | |
inspect_source(cop, ['context "request" do', | |
' include_examples "edges"', | |
' xdescribe "POST #create" do', | |
' end', | |
'', | |
' pending {}', | |
'end']) | |
expect(cop.offenses).to be_empty | |
end | |
it 'handles edge cases 2' do | |
inspect_source(cop, ['context "request" do', | |
' include_examples "edges"', | |
'end']) | |
expect(cop.offenses).to be_empty | |
end | |
describe "#autocorrect" do | |
it "corrects two one-liners" do | |
new_source = autocorrect_source( | |
cop, | |
['context "request" do', | |
' it { is_expected.to be_success }', | |
' it { expect(response.body).to eq "OK" }', | |
'end'] | |
) | |
expect(new_source).to eq( | |
['context "request" do', | |
' it "works", :aggregate_failures do', | |
' is_expected.to be_success', | |
' expect(response.body).to eq "OK"', | |
' end', | |
'end'].join("\n") | |
) | |
end | |
it 'corrects indented one-liners when blank lines and non-example blocks' do | |
new_source = autocorrect_source( | |
cop, | |
['describe "GET #index" do', | |
' context "request" do', | |
' let(:user) { create(:user) } ', | |
' before { get "/" }', | |
'', | |
' it { is_expected.to be_success }', | |
' ', | |
' ', | |
' it { expect(response.body).to eq "OK" }', | |
' end', | |
'end'] | |
) | |
expect(new_source).to eq( | |
['describe "GET #index" do', | |
' context "request" do', | |
' let(:user) { create(:user) } ', | |
' before { get "/" }', | |
'', | |
' it "works", :aggregate_failures do', | |
' is_expected.to be_success', | |
' expect(response.body).to eq "OK"', | |
' end', | |
' end', | |
'end'].join("\n") | |
) | |
end | |
it "corrects several groups" do | |
new_source = autocorrect_source( | |
cop, | |
[ | |
'describe "GET #index" do', | |
' context "request" do', | |
' let(:user) { create(:user) } ', | |
' before { get "/" }', | |
'', | |
' it { is_expected.to be_success }', | |
' ', | |
' ', | |
' it { expect(response.body).to eq "OK" }', | |
'', | |
' context "sub-request", :invalid do', | |
' it { is_expected.not_to be_success }', | |
' it { expect(response.body).to eq "FAILED" }', | |
' end', | |
' end', | |
'end' | |
] | |
) | |
expect(new_source).to eq( | |
['describe "GET #index" do', | |
' context "request" do', | |
' let(:user) { create(:user) } ', | |
' before { get "/" }', | |
'', | |
' it "works", :aggregate_failures do', | |
' is_expected.to be_success', | |
' expect(response.body).to eq "OK"', | |
' end', | |
'', | |
' context "sub-request", :invalid do', | |
' it "works", :aggregate_failures do', | |
' is_expected.not_to be_success', | |
' expect(response.body).to eq "FAILED"', | |
' end', | |
' end', | |
' end', | |
'end'].join("\n") | |
) | |
end | |
end | |
end |
This cop is now a part of the TestProf project: https://test-prof.evilmartians.io/#/rubocop?id=rspecaggregatefailures
And it seems to be fixed: https://github.com/palkan/test-prof/blob/master/lib/test_prof/cops/rspec/aggregate_failures.rb#L64
Thank you @palkan. I Googled this gist after watching your conference talk on youtube and completely missed the gem until later.
It's been a real pleasure following your guides and using the tools in test-prof. Much appreciated!
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Thank you for sharing this!
I had to make a few small adjustments to get it to run at present date. Have a look if you too are having problems:
autocorrect_source
by removing the first argumentcop
and joining the array withjoin("\n")
.