-
-
Notifications
You must be signed in to change notification settings - Fork 276
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Fix #647] Add OrderedHooks cop #1119
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -2973,6 +2973,47 @@ end | |||||
|
||||||
* https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/NotToNot | ||||||
|
||||||
== RSpec/OrderedHooks | ||||||
|
||||||
|=== | ||||||
| Enabled by default | Safe | Supports autocorrection | VersionAdded | VersionChanged | ||||||
|
||||||
| Pending | ||||||
| Yes | ||||||
| Yes | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The PR description says there's no autocorrection yet.
Suggested change
|
||||||
| 2.2 | ||||||
| - | ||||||
|=== | ||||||
|
||||||
Checks that before/around/after hooks are defined in the correct order. | ||||||
|
||||||
If multiple hooks are defined in example group, they should appear in | ||||||
the following order: | ||||||
before :suite | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. But |
||||||
before :context | ||||||
before :example | ||||||
around :example | ||||||
after :example | ||||||
after :context | ||||||
after :suite | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||||||
|
||||||
=== Examples | ||||||
|
||||||
[source,ruby] | ||||||
---- | ||||||
# bad | ||||||
after { run_cleanup } | ||||||
before { run_setup } | ||||||
|
||||||
# good | ||||||
before { run_setup } | ||||||
after { run_cleanup } | ||||||
---- | ||||||
|
||||||
=== References | ||||||
|
||||||
* https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/OrderedHooks | ||||||
|
||||||
== RSpec/OverwritingSetup | ||||||
|
||||||
|=== | ||||||
|
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,86 @@ | ||||||||||||||||||||||||
# frozen_string_literal: true | ||||||||||||||||||||||||
|
||||||||||||||||||||||||
module RuboCop | ||||||||||||||||||||||||
module Cop | ||||||||||||||||||||||||
module RSpec | ||||||||||||||||||||||||
# Checks that before/around/after hooks are defined in the correct order. | ||||||||||||||||||||||||
# | ||||||||||||||||||||||||
# If multiple hooks are defined in example group, they should appear in | ||||||||||||||||||||||||
# the following order: | ||||||||||||||||||||||||
# before :suite | ||||||||||||||||||||||||
# before :context | ||||||||||||||||||||||||
# before :example | ||||||||||||||||||||||||
# around :example | ||||||||||||||||||||||||
# after :example | ||||||||||||||||||||||||
# after :context | ||||||||||||||||||||||||
# after :suite | ||||||||||||||||||||||||
# | ||||||||||||||||||||||||
# @example | ||||||||||||||||||||||||
# # bad | ||||||||||||||||||||||||
# after { run_cleanup } | ||||||||||||||||||||||||
# before { run_setup } | ||||||||||||||||||||||||
# | ||||||||||||||||||||||||
# # good | ||||||||||||||||||||||||
# before { run_setup } | ||||||||||||||||||||||||
# after { run_cleanup } | ||||||||||||||||||||||||
# | ||||||||||||||||||||||||
class OrderedHooks < Base | ||||||||||||||||||||||||
extend AutoCorrector | ||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This can be removed, right? |
||||||||||||||||||||||||
|
||||||||||||||||||||||||
MSG = '`%<hook>s` is supposed to appear before `%<previous>s`' \ | ||||||||||||||||||||||||
' at line %<line>d.' | ||||||||||||||||||||||||
Comment on lines
+30
to
+31
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
RuboCop (Parser) unfortunately cannot check this. |
||||||||||||||||||||||||
|
||||||||||||||||||||||||
EXPECTED_HOOK_ORDER = %i[before around after].freeze | ||||||||||||||||||||||||
EXPECTED_SCOPE_ORDER = %i[suite context each].freeze | ||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe refer to |
||||||||||||||||||||||||
|
||||||||||||||||||||||||
def on_block(node) | ||||||||||||||||||||||||
return unless example_group_with_body?(node) | ||||||||||||||||||||||||
|
||||||||||||||||||||||||
RuboCop::RSpec::ExampleGroup.new(node) | ||||||||||||||||||||||||
.hooks | ||||||||||||||||||||||||
.each_cons(2) { |previous, current| check_order(previous, current) } | ||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ❤️ |
||||||||||||||||||||||||
end | ||||||||||||||||||||||||
|
||||||||||||||||||||||||
private | ||||||||||||||||||||||||
|
||||||||||||||||||||||||
def check_order(previous, current) | ||||||||||||||||||||||||
previous_idx = EXPECTED_HOOK_ORDER.index(previous.name) | ||||||||||||||||||||||||
current_idx = EXPECTED_HOOK_ORDER.index(current.name) | ||||||||||||||||||||||||
|
||||||||||||||||||||||||
if previous_idx == current_idx | ||||||||||||||||||||||||
check_scope_order(previous, current) | ||||||||||||||||||||||||
elsif previous_idx > current_idx | ||||||||||||||||||||||||
order_violation(previous, current) | ||||||||||||||||||||||||
end | ||||||||||||||||||||||||
end | ||||||||||||||||||||||||
|
||||||||||||||||||||||||
def check_scope_order(previous, current) | ||||||||||||||||||||||||
previous_idx = EXPECTED_SCOPE_ORDER.index(previous.scope) | ||||||||||||||||||||||||
current_idx = EXPECTED_SCOPE_ORDER.index(current.scope) | ||||||||||||||||||||||||
|
||||||||||||||||||||||||
if current.name == :after # for after we expect reversed order | ||||||||||||||||||||||||
order_violation(previous, current) if previous_idx < current_idx | ||||||||||||||||||||||||
elsif previous_idx > current_idx | ||||||||||||||||||||||||
order_violation(previous, current) | ||||||||||||||||||||||||
end | ||||||||||||||||||||||||
Comment on lines
+61
to
+65
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I was hoping to get a more symmetric comparison of
Suggested change
|
||||||||||||||||||||||||
end | ||||||||||||||||||||||||
|
||||||||||||||||||||||||
def order_violation(previous, current) | ||||||||||||||||||||||||
message = format(MSG, | ||||||||||||||||||||||||
hook: format_hook(current), | ||||||||||||||||||||||||
previous: format_hook(previous), | ||||||||||||||||||||||||
line: previous.to_node.loc.line) | ||||||||||||||||||||||||
|
||||||||||||||||||||||||
add_offense(current.to_node.send_node, message: message) | ||||||||||||||||||||||||
end | ||||||||||||||||||||||||
|
||||||||||||||||||||||||
def format_hook(hook) | ||||||||||||||||||||||||
msg = hook.name.to_s | ||||||||||||||||||||||||
raw_scope_name = hook.send(:scope_name) | ||||||||||||||||||||||||
msg += "(:#{raw_scope_name})" if raw_scope_name | ||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Perhaps add the scope only when the hooks are identical? To avoid messages like “ |
||||||||||||||||||||||||
msg | ||||||||||||||||||||||||
end | ||||||||||||||||||||||||
end | ||||||||||||||||||||||||
end | ||||||||||||||||||||||||
end | ||||||||||||||||||||||||
end |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,213 @@ | ||
# frozen_string_literal: true | ||
|
||
RSpec.describe RuboCop::Cop::RSpec::OrderedHooks do | ||
it 'detects `before` after `around`' do | ||
expect_offense(<<~RUBY) | ||
describe "hooks order" do | ||
around do |example| | ||
example.call | ||
end | ||
|
||
before do | ||
^^^^^^ `before` is supposed to appear before `around` at line 2. | ||
run_setup | ||
end | ||
end | ||
RUBY | ||
end | ||
|
||
it 'detects `before` after `after`' do | ||
expect_offense(<<~RUBY) | ||
describe "hooks order" do | ||
after do | ||
run_teardown | ||
end | ||
|
||
before do | ||
^^^^^^ `before` is supposed to appear before `after` at line 2. | ||
run_setup | ||
end | ||
end | ||
RUBY | ||
end | ||
|
||
it 'detects `around` after `after`' do | ||
expect_offense(<<~RUBY) | ||
describe "hooks order" do | ||
after do | ||
run_teardown | ||
end | ||
|
||
around do |example| | ||
^^^^^^ `around` is supposed to appear before `after` at line 2. | ||
example.run | ||
end | ||
end | ||
RUBY | ||
end | ||
|
||
context 'with multiple hooks' do | ||
it 'reports each violation independently' do | ||
expect_offense(<<~RUBY) | ||
describe "hooks order" do | ||
after { cleanup } | ||
|
||
around do |example| | ||
^^^^^^ `around` is supposed to appear before `after` at line 2. | ||
example.call | ||
end | ||
|
||
before { some_preparation } | ||
^^^^^^ `before` is supposed to appear before `around` at line 4. | ||
|
||
before { some_other_preparation } | ||
|
||
after { more_cleanup } | ||
|
||
before { more_preparation } | ||
^^^^^^ `before` is supposed to appear before `after` at line 12. | ||
end | ||
RUBY | ||
end | ||
end | ||
|
||
context 'with scoped hooks' do | ||
context 'when hook is before' do | ||
it 'detects `suite` after `context`' do | ||
expect_offense(<<~RUBY) | ||
describe "hooks order" do | ||
before(:context) { init_factories } | ||
before(:suite) { global_setup } | ||
^^^^^^^^^^^^^^ `before(:suite)` is supposed to appear before `before(:context)` at line 2. | ||
end | ||
RUBY | ||
end | ||
|
||
it 'detects `suite` after `each`' do | ||
expect_offense(<<~RUBY) | ||
describe "hooks order" do | ||
before(:each) { init_data } | ||
before(:suite) { global_setup } | ||
^^^^^^^^^^^^^^ `before(:suite)` is supposed to appear before `before(:each)` at line 2. | ||
end | ||
RUBY | ||
end | ||
|
||
it 'detects `context` after `each`' do | ||
expect_offense(<<~RUBY) | ||
describe "hooks order" do | ||
before(:each) { init_data } | ||
before(:context) { setup } | ||
^^^^^^^^^^^^^^^^ `before(:context)` is supposed to appear before `before(:each)` at line 2. | ||
end | ||
RUBY | ||
end | ||
|
||
it 'accepts `example` and `each`' do | ||
expect_no_offenses(<<~RUBY) | ||
describe "hooks order" do | ||
before { setup1 } | ||
before(:each) { setup2 } | ||
before(:example) { setup3 } | ||
end | ||
RUBY | ||
end | ||
|
||
it 'detects `context` after `example`' do | ||
expect_offense(<<~RUBY) | ||
describe "hooks order" do | ||
before(:example) { init_data } | ||
before(:context) { setup } | ||
^^^^^^^^^^^^^^^^ `before(:context)` is supposed to appear before `before(:example)` at line 2. | ||
end | ||
RUBY | ||
end | ||
end | ||
|
||
context 'when hook is after' do | ||
it 'detects `context` after `suite`' do | ||
expect_offense(<<~RUBY) | ||
describe "hooks order" do | ||
after(:suite) { global_teardown } | ||
after(:context) { teardown } | ||
^^^^^^^^^^^^^^^ `after(:context)` is supposed to appear before `after(:suite)` at line 2. | ||
end | ||
RUBY | ||
end | ||
|
||
it 'detects `each` after `suite`' do | ||
expect_offense(<<~RUBY) | ||
describe "hooks order" do | ||
after(:suite) { global_teardown } | ||
after(:each) { teardown } | ||
^^^^^^^^^^^^ `after(:each)` is supposed to appear before `after(:suite)` at line 2. | ||
end | ||
RUBY | ||
end | ||
|
||
it 'detects `each` after `context`' do | ||
expect_offense(<<~RUBY) | ||
describe "hooks order" do | ||
after(:context) { teardown } | ||
after(:each) { cleanup } | ||
^^^^^^^^^^^^ `after(:each)` is supposed to appear before `after(:context)` at line 2. | ||
end | ||
RUBY | ||
end | ||
|
||
it 'accepts `example` and `each`' do | ||
expect_no_offenses(<<~RUBY) | ||
describe "hooks order" do | ||
after { setup1 } | ||
after(:each) { setup2 } | ||
after(:example) { setup3 } | ||
end | ||
RUBY | ||
end | ||
|
||
it 'detects `example` after `context`' do | ||
expect_offense(<<~RUBY) | ||
describe "hooks order" do | ||
after(:context) { cleanup } | ||
after(:example) { teardown } | ||
^^^^^^^^^^^^^^^ `after(:example)` is supposed to appear before `after(:context)` at line 2. | ||
end | ||
RUBY | ||
end | ||
end | ||
end | ||
|
||
it 'accepts hooks in order' do | ||
expect_no_offenses(<<~RUBY) | ||
desribe "correctly ordered hooks" do | ||
before(:suite) do | ||
run_global_setup | ||
end | ||
|
||
before(:context) do | ||
run_context_setup | ||
end | ||
|
||
before(:example) do | ||
run_setup | ||
end | ||
|
||
around(:example) do |example| | ||
example.run | ||
end | ||
|
||
after(:example) do | ||
run_teardown | ||
end | ||
|
||
after(:context) do | ||
run_context_teardown | ||
end | ||
|
||
after(:suite) do | ||
run_global_teardown | ||
end | ||
end | ||
RUBY | ||
end | ||
end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.