From ac953d809fa2ed919f115ee87ae7752ed4b32a35 Mon Sep 17 00:00:00 2001 From: Benjamin Quorning Date: Tue, 9 Jun 2020 16:37:52 +0200 Subject: [PATCH] Use the new Autocorrection API The autocorrection API was changed in Rubocop v0.87.0 (pull request https://github.com/rubocop-hq/rubocop/pull/7868). Here, I change the superclass of `RuboCop::Cop::RSpec::Cop` from `::RuboCop::Cop::Cop` to `::RuboCop::Cop::Base`. This has a few consequences: - Our `#message` methods get called with a different argument than before. It *can* be customized by defining a `#callback_argument` method, but I think it is clearer to avoid callbacks altogether. - Our `#autocorrect` methods don't get called anymore. Instead, we extend `Autocorrector`, and the `corrector` is being yielded when calling `#add_offense`, so the code is mostly moved in there. For some cases, this means that some code can be removed, which is nice. For some cases, it means that the methods get too long, or the code complexity gets too high, and in those cases I chose to just call out to an `#autocorrect` method anyway, but of course passing the `corrector` and any usable local variables along. This also means we bump the dependency of RuboCop quite a bit, from '>= 0.68.1' to '>= 0.87.0'. --- .rubocop_todo.yml | 2 +- lib/rubocop/cop/rspec/align_left_let_brace.rb | 29 +++---- .../cop/rspec/align_right_let_brace.rb | 29 +++---- lib/rubocop/cop/rspec/be.rb | 2 +- lib/rubocop/cop/rspec/be_eql.rb | 10 +-- .../capybara/current_path_expectation.rb | 34 +++++---- .../cop/rspec/capybara/feature_methods.rb | 17 ++--- lib/rubocop/cop/rspec/context_method.rb | 12 ++- lib/rubocop/cop/rspec/cop.rb | 2 +- lib/rubocop/cop/rspec/described_class.rb | 17 +++-- lib/rubocop/cop/rspec/dialect.rb | 15 +--- lib/rubocop/cop/rspec/empty_hook.rb | 14 ++-- .../cop/rspec/empty_line_after_example.rb | 8 +- .../rspec/empty_line_after_example_group.rb | 10 +-- .../cop/rspec/empty_line_after_final_let.rb | 5 +- .../cop/rspec/empty_line_after_hook.rb | 10 +-- .../cop/rspec/empty_line_after_subject.rb | 5 +- lib/rubocop/cop/rspec/example_wording.rb | 13 ++-- lib/rubocop/cop/rspec/expect_actual.rb | 17 ++--- lib/rubocop/cop/rspec/expect_change.rb | 43 +++-------- lib/rubocop/cop/rspec/expect_in_hook.rb | 4 +- lib/rubocop/cop/rspec/expect_output.rb | 2 +- .../attribute_defined_statically.rb | 36 ++++----- .../cop/rspec/factory_bot/create_list.rb | 26 +++---- .../rspec/factory_bot/factory_class_name.rb | 13 ++-- lib/rubocop/cop/rspec/hook_argument.rb | 28 +++---- .../cop/rspec/hooks_before_examples.rb | 25 +++--- lib/rubocop/cop/rspec/implicit_expect.rb | 20 ++--- lib/rubocop/cop/rspec/implicit_subject.rb | 13 ++-- lib/rubocop/cop/rspec/instance_spy.rb | 28 ++++--- .../cop/rspec/invalid_predicate_matcher.rb | 7 +- lib/rubocop/cop/rspec/it_behaves_like.rb | 9 +-- lib/rubocop/cop/rspec/leading_subject.rb | 26 +++---- lib/rubocop/cop/rspec/let_before_examples.rb | 22 +++--- lib/rubocop/cop/rspec/message_chain.rb | 11 +-- lib/rubocop/cop/rspec/message_expectation.rb | 2 +- lib/rubocop/cop/rspec/message_spies.rb | 3 +- lib/rubocop/cop/rspec/multiple_subjects.rb | 35 +++++---- lib/rubocop/cop/rspec/named_subject.rb | 2 +- lib/rubocop/cop/rspec/not_to_not.rb | 9 +-- lib/rubocop/cop/rspec/predicate_matcher.rb | 76 ++++++------------- lib/rubocop/cop/rspec/rails/http_status.rb | 12 +-- lib/rubocop/cop/rspec/receive_counts.rb | 30 ++++---- lib/rubocop/cop/rspec/receive_never.rb | 20 ++--- lib/rubocop/cop/rspec/return_from_stub.rb | 27 ++----- lib/rubocop/cop/rspec/scattered_let.rb | 17 ++--- lib/rubocop/cop/rspec/shared_context.rb | 27 ++----- lib/rubocop/cop/rspec/shared_examples.rb | 13 ++-- .../rspec/single_argument_message_chain.rb | 31 ++++---- lib/rubocop/cop/rspec/yield.rb | 23 +++--- lib/rubocop/rspec/blank_line_separation.rb | 8 -- rubocop-rspec.gemspec | 2 +- spec/rubocop/cop/rspec/expect_actual_spec.rb | 4 +- tasks/cops_documentation.rake | 2 +- 54 files changed, 385 insertions(+), 522 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index d6a65b650..3846efa04 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -14,4 +14,4 @@ InternalAffairs/MethodNameEqual: # Offense count: 1 # Configuration parameters: CountComments. Metrics/ClassLength: - Max: 104 + Max: 106 diff --git a/lib/rubocop/cop/rspec/align_left_let_brace.rb b/lib/rubocop/cop/rspec/align_left_let_brace.rb index 08669f3aa..b2f307642 100644 --- a/lib/rubocop/cop/rspec/align_left_let_brace.rb +++ b/lib/rubocop/cop/rspec/align_left_let_brace.rb @@ -18,35 +18,28 @@ module RSpec # let(:a) { b } # class AlignLeftLetBrace < Cop + extend AutoCorrector + MSG = 'Align left let brace' def self.autocorrect_incompatible_with [Layout::ExtraSpacing] end - def investigate(_processed_source) + def on_new_investigation return if processed_source.blank? - token_aligner.offending_tokens.each do |let| - add_offense(let, location: :begin) - end - end + token_aligner = + RuboCop::RSpec::AlignLetBrace.new(processed_source.ast, :begin) - def autocorrect(let) - lambda do |corrector| - corrector.insert_before( - let.loc.begin, - token_aligner.indent_for(let) - ) + token_aligner.offending_tokens.each do |let| + add_offense(let.loc.begin) do |corrector| + corrector.insert_before( + let.loc.begin, token_aligner.indent_for(let) + ) + end end end - - private - - def token_aligner - @token_aligner ||= - RuboCop::RSpec::AlignLetBrace.new(processed_source.ast, :begin) - end end end end diff --git a/lib/rubocop/cop/rspec/align_right_let_brace.rb b/lib/rubocop/cop/rspec/align_right_let_brace.rb index 0526a09ac..387d9507b 100644 --- a/lib/rubocop/cop/rspec/align_right_let_brace.rb +++ b/lib/rubocop/cop/rspec/align_right_let_brace.rb @@ -18,35 +18,28 @@ module RSpec # let(:a) { b } # class AlignRightLetBrace < Cop + extend AutoCorrector + MSG = 'Align right let brace' def self.autocorrect_incompatible_with [Layout::ExtraSpacing] end - def investigate(_processed_source) + def on_new_investigation return if processed_source.blank? - token_aligner.offending_tokens.each do |let| - add_offense(let, location: :end) - end - end + token_aligner = + RuboCop::RSpec::AlignLetBrace.new(processed_source.ast, :end) - def autocorrect(let) - lambda do |corrector| - corrector.insert_before( - let.loc.end, - token_aligner.indent_for(let) - ) + token_aligner.offending_tokens.each do |let| + add_offense(let.loc.end) do |corrector| + corrector.insert_before( + let.loc.end, token_aligner.indent_for(let) + ) + end end end - - private - - def token_aligner - @token_aligner ||= - RuboCop::RSpec::AlignLetBrace.new(processed_source.ast, :end) - end end end end diff --git a/lib/rubocop/cop/rspec/be.rb b/lib/rubocop/cop/rspec/be.rb index 503648e12..a70cb0044 100644 --- a/lib/rubocop/cop/rspec/be.rb +++ b/lib/rubocop/cop/rspec/be.rb @@ -28,7 +28,7 @@ class Be < Cop def on_send(node) be_without_args(node) do |matcher| - add_offense(matcher, location: :selector) + add_offense(matcher.loc.selector) end end end diff --git a/lib/rubocop/cop/rspec/be_eql.rb b/lib/rubocop/cop/rspec/be_eql.rb index 0721c8c53..ad13a904a 100644 --- a/lib/rubocop/cop/rspec/be_eql.rb +++ b/lib/rubocop/cop/rspec/be_eql.rb @@ -36,6 +36,8 @@ module RSpec # coerce objects for comparison. # class BeEql < Cop + extend AutoCorrector + MSG = 'Prefer `be` over `eql`.' def_node_matcher :eql_type_with_identity, <<-PATTERN @@ -44,13 +46,11 @@ class BeEql < Cop def on_send(node) eql_type_with_identity(node) do |eql| - add_offense(eql, location: :selector) + add_offense(eql.loc.selector) do |corrector| + corrector.replace(eql.loc.selector, 'be') + end end end - - def autocorrect(node) - ->(corrector) { corrector.replace(node.loc.selector, 'be') } - end end end end diff --git a/lib/rubocop/cop/rspec/capybara/current_path_expectation.rb b/lib/rubocop/cop/rspec/capybara/current_path_expectation.rb index c69303161..ec51d2c85 100644 --- a/lib/rubocop/cop/rspec/capybara/current_path_expectation.rb +++ b/lib/rubocop/cop/rspec/capybara/current_path_expectation.rb @@ -24,6 +24,8 @@ module Capybara # expect(page).to have_current_path(/widgets/) # class CurrentPathExpectation < Cop + extend AutoCorrector + MSG = 'Do not set an RSpec expectation on `current_path` in ' \ 'Capybara feature specs - instead, use the ' \ '`have_current_path` matcher on `page`' @@ -47,30 +49,30 @@ class CurrentPathExpectation < Cop def on_send(node) expectation_set_on_current_path(node) do - add_offense(node, location: :selector) + add_offense(node.loc.selector) do |corrector| + next unless node.chained? + + autocorrect(corrector, node) + end end end - def autocorrect(node) - lambda do |corrector| - return unless node.chained? + private - as_is_matcher(node.parent) do |to_sym, matcher_node| - rewrite_expectation(corrector, node, to_sym, matcher_node) - end + def autocorrect(corrector, node) + as_is_matcher(node.parent) do |to_sym, matcher_node| + rewrite_expectation(corrector, node, to_sym, matcher_node) + end - regexp_str_matcher(node.parent) do |to_sym, matcher_node, regexp| - rewrite_expectation(corrector, node, to_sym, matcher_node) - convert_regexp_str_to_literal(corrector, matcher_node, regexp) - end + regexp_str_matcher(node.parent) do |to_sym, matcher_node, regexp| + rewrite_expectation(corrector, node, to_sym, matcher_node) + convert_regexp_str_to_literal(corrector, matcher_node, regexp) end end - private - def rewrite_expectation(corrector, node, to_symbol, matcher_node) current_path_node = node.first_argument - corrector.replace(current_path_node.loc.expression, 'page') + corrector.replace(current_path_node, 'page') corrector.replace(node.parent.loc.selector, 'to') matcher_method = if to_symbol == :to 'have_current_path' @@ -84,7 +86,7 @@ def rewrite_expectation(corrector, node, to_symbol, matcher_node) def convert_regexp_str_to_literal(corrector, matcher_node, regexp_str) str_node = matcher_node.first_argument regexp_expr = Regexp.new(regexp_str).inspect - corrector.replace(str_node.loc.expression, regexp_expr) + corrector.replace(str_node, regexp_expr) end # `have_current_path` with no options will include the querystring @@ -97,7 +99,7 @@ def add_ignore_query_options(corrector, node) return if %i[regexp str].include?(expectation_last_child.type) corrector.insert_after( - expectation_last_child.loc.expression, + expectation_last_child, ', ignore_query: true' ) end diff --git a/lib/rubocop/cop/rspec/capybara/feature_methods.rb b/lib/rubocop/cop/rspec/capybara/feature_methods.rb index f6681fe28..c8e837420 100644 --- a/lib/rubocop/cop/rspec/capybara/feature_methods.rb +++ b/lib/rubocop/cop/rspec/capybara/feature_methods.rb @@ -41,6 +41,8 @@ module Capybara # end # end class FeatureMethods < Cop + extend AutoCorrector + MSG = 'Use `%s` instead of `%s`.' # https://git.io/v7Kwr @@ -71,18 +73,15 @@ def on_block(node) feature_method(node) do |send_node, match| next if enabled?(match) - add_offense( - send_node, - location: :selector, - message: format(MSG, method: match, replacement: MAP[match]) - ) + add_offense(send_node.loc.selector) do |corrector| + corrector.replace(send_node.loc.selector, MAP[match].to_s) + end end end - def autocorrect(node) - lambda do |corrector| - corrector.replace(node.loc.selector, MAP[node.method_name].to_s) - end + def message(range) + name = range.source.to_sym + format(MSG, method: name, replacement: MAP[name]) end private diff --git a/lib/rubocop/cop/rspec/context_method.rb b/lib/rubocop/cop/rspec/context_method.rb index 23061ad3f..823947236 100644 --- a/lib/rubocop/cop/rspec/context_method.rb +++ b/lib/rubocop/cop/rspec/context_method.rb @@ -24,6 +24,8 @@ module RSpec # # ... # end class ContextMethod < Cop + extend AutoCorrector + MSG = 'Use `describe` for testing methods.' def_node_matcher :context_method, <<-PATTERN @@ -32,13 +34,9 @@ class ContextMethod < Cop def on_block(node) context_method(node) do |context| - add_offense(context) - end - end - - def autocorrect(node) - lambda do |corrector| - corrector.replace(node.parent.loc.selector, 'describe') + add_offense(context) do |corrector| + corrector.replace(node.send_node.loc.selector, 'describe') + end end end diff --git a/lib/rubocop/cop/rspec/cop.rb b/lib/rubocop/cop/rspec/cop.rb index 1c8cf0086..d9f09d4f1 100644 --- a/lib/rubocop/cop/rspec/cop.rb +++ b/lib/rubocop/cop/rspec/cop.rb @@ -17,7 +17,7 @@ module RSpec # # Patterns: # # - '_test.rb$' # # - '(?:^|/)test/' - class Cop < ::RuboCop::Cop::Cop + class Cop < ::RuboCop::Cop::Base include RuboCop::RSpec::Language include RuboCop::RSpec::Language::NodePattern diff --git a/lib/rubocop/cop/rspec/described_class.rb b/lib/rubocop/cop/rspec/described_class.rb index cbe453858..222b8f9db 100644 --- a/lib/rubocop/cop/rspec/described_class.rb +++ b/lib/rubocop/cop/rspec/described_class.rb @@ -55,6 +55,7 @@ module RSpec # end # class DescribedClass < Cop + extend AutoCorrector include ConfigurableEnforcedStyle DESCRIBED_CLASS = 'described_class' @@ -85,22 +86,24 @@ def on_block(node) return unless body find_usage(body) do |match| - add_offense(match, message: message(match.const_name)) + msg = message(match.const_name) + add_offense(match, message: msg) do |corrector| + autocorrect(corrector, match) + end end end - def autocorrect(node) + private + + def autocorrect(corrector, match) replacement = if style == :described_class DESCRIBED_CLASS else @described_class.const_name end - lambda do |corrector| - corrector.replace(node.loc.expression, replacement) - end - end - private + corrector.replace(match, replacement) + end def find_usage(node, &block) yield(node) if offensive?(node) diff --git a/lib/rubocop/cop/rspec/dialect.rb b/lib/rubocop/cop/rspec/dialect.rb index 137359483..1ad00ff9f 100644 --- a/lib/rubocop/cop/rspec/dialect.rb +++ b/lib/rubocop/cop/rspec/dialect.rb @@ -42,6 +42,7 @@ module RSpec # # ... # end class Dialect < Cop + extend AutoCorrector include MethodPreference MSG = 'Prefer `%s` over `%s`.' @@ -52,24 +53,16 @@ def on_send(node) return unless rspec_method?(node) return unless preferred_methods[node.method_name] - add_offense(node) - end + msg = format(MSG, prefer: preferred_method(node.method_name), + current: node.method_name) - def autocorrect(node) - lambda do |corrector| + add_offense(node, message: msg) do |corrector| current = node.loc.selector preferred = preferred_method(current.source) corrector.replace(current, preferred) end end - - private - - def message(node) - format(MSG, prefer: preferred_method(node.method_name), - current: node.method_name) - end end end end diff --git a/lib/rubocop/cop/rspec/empty_hook.rb b/lib/rubocop/cop/rspec/empty_hook.rb index ea5618032..4e62cd95f 100644 --- a/lib/rubocop/cop/rspec/empty_hook.rb +++ b/lib/rubocop/cop/rspec/empty_hook.rb @@ -23,6 +23,7 @@ module RSpec # end # after(:all) { cleanup_feed } class EmptyHook < Cop + extend AutoCorrector include RuboCop::Cop::RangeHelp MSG = 'Empty hook detected.' @@ -33,15 +34,10 @@ class EmptyHook < Cop def on_block(node) empty_hook?(node) do |hook| - add_offense(hook) - end - end - - def autocorrect(node) - lambda do |corrector| - block = node.parent - range = range_with_surrounding_space(range: block.loc.expression) - corrector.remove(range) + add_offense(hook) do |corrector| + range = range_with_surrounding_space(range: node.loc.expression) + corrector.remove(range) + end end end end diff --git a/lib/rubocop/cop/rspec/empty_line_after_example.rb b/lib/rubocop/cop/rspec/empty_line_after_example.rb index 7ddd05632..fb8287470 100644 --- a/lib/rubocop/cop/rspec/empty_line_after_example.rb +++ b/lib/rubocop/cop/rspec/empty_line_after_example.rb @@ -42,6 +42,7 @@ module RSpec # end # class EmptyLineAfterExample < Cop + extend AutoCorrector include RuboCop::RSpec::BlankLineSeparation MSG = 'Add an empty line after `%s`.' @@ -52,9 +53,10 @@ def on_block(node) return if allowed_one_liner?(node) missing_separating_line(node) do |location| - add_offense(node, - location: location, - message: format(MSG, example: node.method_name)) + msg = format(MSG, example: node.method_name) + add_offense(location, message: msg) do |corrector| + corrector.insert_after(location.end, "\n") + end end end diff --git a/lib/rubocop/cop/rspec/empty_line_after_example_group.rb b/lib/rubocop/cop/rspec/empty_line_after_example_group.rb index 7b87add04..5fe23fc54 100644 --- a/lib/rubocop/cop/rspec/empty_line_after_example_group.rb +++ b/lib/rubocop/cop/rspec/empty_line_after_example_group.rb @@ -24,6 +24,7 @@ module RSpec # end # class EmptyLineAfterExampleGroup < Cop + extend AutoCorrector include RuboCop::RSpec::BlankLineSeparation MSG = 'Add an empty line after `%s`.' @@ -33,11 +34,10 @@ def on_block(node) return if last_child?(node) missing_separating_line(node) do |location| - add_offense( - node, - location: location, - message: format(MSG, example_group: node.method_name) - ) + msg = format(MSG, example_group: node.method_name) + add_offense(location, message: msg) do |corrector| + corrector.insert_after(location.end, "\n") + end end end end diff --git a/lib/rubocop/cop/rspec/empty_line_after_final_let.rb b/lib/rubocop/cop/rspec/empty_line_after_final_let.rb index a059433ae..04a9537ef 100644 --- a/lib/rubocop/cop/rspec/empty_line_after_final_let.rb +++ b/lib/rubocop/cop/rspec/empty_line_after_final_let.rb @@ -17,6 +17,7 @@ module RSpec # # it { does_something } class EmptyLineAfterFinalLet < Cop + extend AutoCorrector include RuboCop::RSpec::BlankLineSeparation MSG = 'Add an empty line after the last `let` block.' @@ -30,7 +31,9 @@ def on_block(node) return if last_child?(latest_let) missing_separating_line(latest_let) do |location| - add_offense(latest_let, location: location) + add_offense(location) do |corrector| + corrector.insert_after(location.end, "\n") + end end end end diff --git a/lib/rubocop/cop/rspec/empty_line_after_hook.rb b/lib/rubocop/cop/rspec/empty_line_after_hook.rb index 678534317..1b5f49881 100644 --- a/lib/rubocop/cop/rspec/empty_line_after_hook.rb +++ b/lib/rubocop/cop/rspec/empty_line_after_hook.rb @@ -34,6 +34,7 @@ module RSpec # it { does_something } # class EmptyLineAfterHook < Cop + extend AutoCorrector include RuboCop::RSpec::BlankLineSeparation MSG = 'Add an empty line after `%s`.' @@ -43,11 +44,10 @@ def on_block(node) return if last_child?(node) missing_separating_line(node) do |location| - add_offense( - node, - location: location, - message: format(MSG, hook: node.method_name) - ) + msg = format(MSG, hook: node.method_name) + add_offense(location, message: msg) do |corrector| + corrector.insert_after(location.end, "\n") + end end end end diff --git a/lib/rubocop/cop/rspec/empty_line_after_subject.rb b/lib/rubocop/cop/rspec/empty_line_after_subject.rb index a0f6ed0f5..2b95671fa 100644 --- a/lib/rubocop/cop/rspec/empty_line_after_subject.rb +++ b/lib/rubocop/cop/rspec/empty_line_after_subject.rb @@ -15,6 +15,7 @@ module RSpec # # let(:foo) { bar } class EmptyLineAfterSubject < Cop + extend AutoCorrector include RuboCop::RSpec::BlankLineSeparation MSG = 'Add empty line after `subject`.' @@ -24,7 +25,9 @@ def on_block(node) return if last_child?(node) missing_separating_line(node) do |location| - add_offense(node, location: location) + add_offense(location) do |corrector| + corrector.insert_after(location.end, "\n") + end end end diff --git a/lib/rubocop/cop/rspec/example_wording.rb b/lib/rubocop/cop/rspec/example_wording.rb index 5407544e9..bef638e1d 100644 --- a/lib/rubocop/cop/rspec/example_wording.rb +++ b/lib/rubocop/cop/rspec/example_wording.rb @@ -30,6 +30,8 @@ module RSpec # it 'does things' do # end class ExampleWording < Cop + extend AutoCorrector + MSG_SHOULD = 'Do not use should when describing your tests.' MSG_IT = "Do not repeat 'it' when describing your tests." @@ -53,16 +55,13 @@ def on_block(node) end end - def autocorrect(node) - lambda do |corrector| - corrector.replace(docstring(node), replacement_text(node)) - end - end - private def add_wording_offense(node, message) - add_offense(node, location: docstring(node), message: message) + docstring = docstring(node) + add_offense(docstring, message: message) do |corrector| + corrector.replace(docstring, replacement_text(node)) + end end def docstring(node) diff --git a/lib/rubocop/cop/rspec/expect_actual.rb b/lib/rubocop/cop/rspec/expect_actual.rb index 3bffe2179..d01b28a46 100644 --- a/lib/rubocop/cop/rspec/expect_actual.rb +++ b/lib/rubocop/cop/rspec/expect_actual.rb @@ -17,6 +17,8 @@ module RSpec # expect(name).to eq("John") # class ExpectActual < Cop + extend AutoCorrector + MSG = 'Provide the actual you are testing to `expect(...)`.' SIMPLE_LITERALS = %i[ @@ -55,17 +57,12 @@ class ExpectActual < Cop PATTERN def on_send(node) - expect_literal(node) do |argument| - add_offense(node, location: argument.source_range) - end - end - - def autocorrect(node) - actual, matcher, expected = expect_literal(node) - lambda do |corrector| - return unless SUPPORTED_MATCHERS.include?(matcher) + expect_literal(node) do |actual, matcher, expected| + add_offense(actual.source_range) do |corrector| + next unless SUPPORTED_MATCHERS.include?(matcher) - swap(corrector, actual, expected) + swap(corrector, actual, expected) + end end end diff --git a/lib/rubocop/cop/rspec/expect_change.rb b/lib/rubocop/cop/rspec/expect_change.rb index e2afd909a..7687f9cba 100644 --- a/lib/rubocop/cop/rspec/expect_change.rb +++ b/lib/rubocop/cop/rspec/expect_change.rb @@ -30,6 +30,7 @@ module RSpec # expect { run }.to change { user.reload.name } # class ExpectChange < Cop + extend AutoCorrector include ConfigurableEnforcedStyle MSG_BLOCK = 'Prefer `change(%s, :%s)`.' @@ -51,10 +52,11 @@ def on_send(node) return unless style == :block expect_change_with_arguments(node) do |receiver, message| - add_offense( - node, - message: format(MSG_CALL, obj: receiver, attr: message) - ) + msg = format(MSG_CALL, obj: receiver, attr: message) + add_offense(node, message: msg) do |corrector| + replacement = "change { #{receiver}.#{message} }" + corrector.replace(node, replacement) + end end end @@ -62,37 +64,10 @@ def on_block(node) return unless style == :method_call expect_change_with_block(node) do |receiver, message| - add_offense( - node, - message: format(MSG_BLOCK, obj: receiver, attr: message) - ) - end - end - - def autocorrect(node) - if style == :block - autocorrect_method_call_to_block(node) - else - autocorrect_block_to_method_call(node) - end - end - - private - - def autocorrect_method_call_to_block(node) - lambda do |corrector| - expect_change_with_arguments(node) do |receiver, message| - replacement = "change { #{receiver}.#{message} }" - corrector.replace(node.loc.expression, replacement) - end - end - end - - def autocorrect_block_to_method_call(node) - lambda do |corrector| - expect_change_with_block(node) do |receiver, message| + msg = format(MSG_BLOCK, obj: receiver, attr: message) + add_offense(node, message: msg) do |corrector| replacement = "change(#{receiver}, :#{message})" - corrector.replace(node.loc.expression, replacement) + corrector.replace(node, replacement) end end end diff --git a/lib/rubocop/cop/rspec/expect_in_hook.rb b/lib/rubocop/cop/rspec/expect_in_hook.rb index b0cd8e69b..8bf284ac0 100644 --- a/lib/rubocop/cop/rspec/expect_in_hook.rb +++ b/lib/rubocop/cop/rspec/expect_in_hook.rb @@ -30,8 +30,8 @@ def on_block(node) return if node.body.nil? expectation(node.body) do |expect| - add_offense(expect, location: :selector, - message: message(expect, node)) + add_offense(expect.loc.selector, + message: message(expect, node)) end end diff --git a/lib/rubocop/cop/rspec/expect_output.rb b/lib/rubocop/cop/rspec/expect_output.rb index 9d2e37153..92ec20216 100644 --- a/lib/rubocop/cop/rspec/expect_output.rb +++ b/lib/rubocop/cop/rspec/expect_output.rb @@ -27,7 +27,7 @@ def on_gvasgn(node) name = variable_name[1..-1] return unless name.eql?('stdout') || name.eql?('stderr') - add_offense(node, location: :name, message: format(MSG, name: name)) + add_offense(node.loc.name, message: format(MSG, name: name)) end private diff --git a/lib/rubocop/cop/rspec/factory_bot/attribute_defined_statically.rb b/lib/rubocop/cop/rspec/factory_bot/attribute_defined_statically.rb index b081d0ff7..23afbd8b3 100644 --- a/lib/rubocop/cop/rspec/factory_bot/attribute_defined_statically.rb +++ b/lib/rubocop/cop/rspec/factory_bot/attribute_defined_statically.rb @@ -25,6 +25,8 @@ module FactoryBot # # good # count { 1 } class AttributeDefinedStatically < Cop + extend AutoCorrector + MSG = 'Use a block to declare attribute values.' def_node_matcher :value_matcher, <<-PATTERN @@ -43,20 +45,22 @@ def on_block(node) next unless offensive_receiver?(attribute.receiver, node) next if proc?(attribute) || association?(attribute.first_argument) - add_offense(attribute) + add_offense(attribute) do |corrector| + autocorrect(corrector, attribute) + end end end - def autocorrect(node) + private + + def autocorrect(corrector, node) if node.parenthesized? - autocorrect_replacing_parens(node) + autocorrect_replacing_parens(corrector, node) else - autocorrect_without_parens(node) + autocorrect_without_parens(corrector, node) end end - private - def offensive_receiver?(receiver, node) receiver.nil? || receiver.self_type? || @@ -77,24 +81,20 @@ def proc?(attribute) def_node_matcher :association?, '(hash <(pair (sym :factory) _) ...>)' - def autocorrect_replacing_parens(node) + def autocorrect_replacing_parens(corrector, node) left_braces, right_braces = braces(node) - lambda do |corrector| - corrector.replace(node.location.begin, ' ' + left_braces) - corrector.replace(node.location.end, right_braces) - end + corrector.replace(node.location.begin, ' ' + left_braces) + corrector.replace(node.location.end, right_braces) end - def autocorrect_without_parens(node) + def autocorrect_without_parens(corrector, node) left_braces, right_braces = braces(node) - lambda do |corrector| - argument = node.first_argument - expression = argument.location.expression - corrector.insert_before(expression, left_braces) - corrector.insert_after(expression, right_braces) - end + argument = node.first_argument + expression = argument.location.expression + corrector.insert_before(expression, left_braces) + corrector.insert_after(expression, right_braces) end def braces(node) diff --git a/lib/rubocop/cop/rspec/factory_bot/create_list.rb b/lib/rubocop/cop/rspec/factory_bot/create_list.rb index 11addc723..1c84d4312 100644 --- a/lib/rubocop/cop/rspec/factory_bot/create_list.rb +++ b/lib/rubocop/cop/rspec/factory_bot/create_list.rb @@ -25,6 +25,7 @@ module FactoryBot # # good # 3.times { create :user } class CreateList < Cop + extend AutoCorrector include ConfigurableEnforcedStyle MSG_CREATE_LIST = 'Prefer create_list.' @@ -51,26 +52,19 @@ def on_block(node) return unless n_times_block_without_arg?(node) return unless contains_only_factory?(node.body) - add_offense(node.send_node, message: MSG_CREATE_LIST) + add_offense(node.send_node, message: MSG_CREATE_LIST) do |corrector| + CreateListCorrector.new(node.send_node).call(corrector) + end end def on_send(node) return unless style == :n_times factory_list_call(node) do |_receiver, _factory, count, _| - add_offense( - node, - location: :selector, - message: format(MSG_N_TIMES, number: count) - ) - end - end - - def autocorrect(node) - if style == :create_list - CreateListCorrector.new(node) - else - TimesCorrector.new(node) + message = format(MSG_N_TIMES, number: count) + add_offense(node.loc.selector, message: message) do |corrector| + TimesCorrector.new(node).call(corrector) + end end end @@ -115,7 +109,7 @@ def initialize(node) def call(corrector) replacement = generate_n_times_block(node) - corrector.replace(node.loc.expression, replacement) + corrector.replace(node, replacement) end private @@ -148,7 +142,7 @@ def call(corrector) call_replacement(node) end - corrector.replace(node.loc.expression, replacement) + corrector.replace(node, replacement) end private diff --git a/lib/rubocop/cop/rspec/factory_bot/factory_class_name.rb b/lib/rubocop/cop/rspec/factory_bot/factory_class_name.rb index 3a49f33d9..da2e96728 100644 --- a/lib/rubocop/cop/rspec/factory_bot/factory_class_name.rb +++ b/lib/rubocop/cop/rspec/factory_bot/factory_class_name.rb @@ -20,6 +20,8 @@ module FactoryBot # factory :foo, class: 'Foo' do # end class FactoryClassName < Cop + extend AutoCorrector + MSG = "Pass '%s' string instead of `%s` " \ 'constant.' ALLOWED_CONSTANTS = %w[Hash OpenStruct].freeze @@ -32,13 +34,10 @@ def on_send(node) class_name(node) do |cn| next if allowed?(cn.const_name) - add_offense(cn, message: format(MSG, class_name: cn.const_name)) - end - end - - def autocorrect(node) - lambda do |corrector| - corrector.replace(node.loc.expression, "'#{node.source}'") + msg = format(MSG, class_name: cn.const_name) + add_offense(cn, message: msg) do |corrector| + corrector.replace(cn, "'#{cn.source}'") + end end end diff --git a/lib/rubocop/cop/rspec/hook_argument.rb b/lib/rubocop/cop/rspec/hook_argument.rb index 99411cd90..f62ba0301 100644 --- a/lib/rubocop/cop/rspec/hook_argument.rb +++ b/lib/rubocop/cop/rspec/hook_argument.rb @@ -58,6 +58,7 @@ module RSpec # # ... # end class HookArgument < Cop + extend AutoCorrector include ConfigurableEnforcedStyle IMPLICIT_MSG = 'Omit the default `%p` ' \ @@ -78,18 +79,11 @@ def on_block(node) return check_implicit(method_send) unless scope_name style_detected(scope_name) - add_offense( - method_send, - message: explicit_message(scope_name) - ) - end - end - - def autocorrect(node) - scope = implicit_style? ? '' : "(#{style.inspect})" - - lambda do |corrector| - corrector.replace(argument_range(node), scope) + msg = explicit_message(scope_name) + add_offense(method_send, message: msg) do |corrector| + scope = implicit_style? ? '' : "(#{style.inspect})" + corrector.replace(argument_range(method_send), scope) + end end end @@ -99,11 +93,11 @@ def check_implicit(method_send) style_detected(:implicit) return if implicit_style? - add_offense( - method_send, - location: :selector, - message: format(EXPLICIT_MSG, scope: style) - ) + msg = explicit_message(nil) + add_offense(method_send.loc.selector, message: msg) do |corrector| + scope = "(#{style.inspect})" + corrector.replace(argument_range(method_send), scope) + end end def explicit_message(scope) diff --git a/lib/rubocop/cop/rspec/hooks_before_examples.rb b/lib/rubocop/cop/rspec/hooks_before_examples.rb index 1c924da42..dcaba4ea1 100644 --- a/lib/rubocop/cop/rspec/hooks_before_examples.rb +++ b/lib/rubocop/cop/rspec/hooks_before_examples.rb @@ -24,6 +24,8 @@ module RSpec # end # class HooksBeforeExamples < Cop + extend AutoCorrector + MSG = 'Move `%s` above the examples in the group.' def_node_matcher :example_or_group?, <<-PATTERN @@ -39,15 +41,6 @@ def on_block(node) check_hooks(node.body) if multiline_block?(node.body) end - def autocorrect(node) - lambda do |corrector| - first_example = find_first_example(node.parent) - RuboCop::RSpec::Corrector::MoveNode.new( - node, corrector, processed_source - ).move_before(first_example) - end - end - private def multiline_block?(block) @@ -62,16 +55,22 @@ def check_hooks(node) next if child.sibling_index < first_example.sibling_index next unless hook?(child) - add_offense( - child, - message: format(MSG, hook: child.method_name) - ) + msg = format(MSG, hook: child.method_name) + add_offense(child, message: msg) do |corrector| + autocorrect(corrector, child, first_example) + end end end def find_first_example(node) node.children.find { |sibling| example_or_group?(sibling) } end + + def autocorrect(corrector, node, first_example) + RuboCop::RSpec::Corrector::MoveNode.new( + node, corrector, processed_source + ).move_before(first_example) + end end end end diff --git a/lib/rubocop/cop/rspec/implicit_expect.rb b/lib/rubocop/cop/rspec/implicit_expect.rb index ca3184bf8..d691df2a9 100644 --- a/lib/rubocop/cop/rspec/implicit_expect.rb +++ b/lib/rubocop/cop/rspec/implicit_expect.rb @@ -25,6 +25,7 @@ module RSpec # it { should be_truthy } # class ImplicitExpect < Cop + extend AutoCorrector include ConfigurableEnforcedStyle MSG = 'Prefer `%s` over `%s`.' @@ -54,20 +55,11 @@ def on_send(node) # rubocop:disable Metrics/MethodLength else opposite_style_detected - add_offense( - node, - location: source_range, - message: offense_message(expectation_source) - ) - end - end - - def autocorrect(node) - lambda do |corrector| - offense = offending_expect(node) - replacement = replacement_source(offense.source) - - corrector.replace(offense, replacement) + msg = offense_message(expectation_source) + add_offense(source_range, message: msg) do |corrector| + replacement = replacement_source(expectation_source) + corrector.replace(source_range, replacement) + end end end diff --git a/lib/rubocop/cop/rspec/implicit_subject.rb b/lib/rubocop/cop/rspec/implicit_subject.rb index b3deba158..7af5bc299 100644 --- a/lib/rubocop/cop/rspec/implicit_subject.rb +++ b/lib/rubocop/cop/rspec/implicit_subject.rb @@ -27,6 +27,7 @@ module RSpec # it { expect(subject).to be_truthy } # class ImplicitSubject < Cop + extend AutoCorrector include ConfigurableEnforcedStyle MSG = "Don't use implicit subject." @@ -39,10 +40,14 @@ def on_send(node) return unless implicit_subject?(node) return if valid_usage?(node) - add_offense(node) + add_offense(node) do |corrector| + autocorrect(corrector, node) + end end - def autocorrect(node) + private + + def autocorrect(corrector, node) replacement = 'expect(subject)' if node.method_name == :should replacement += '.to' @@ -50,11 +55,9 @@ def autocorrect(node) replacement += '.not_to' end - ->(corrector) { corrector.replace(node.loc.selector, replacement) } + corrector.replace(node.loc.selector, replacement) end - private - def valid_usage?(node) example = node.ancestors.find { |parent| example?(parent) } return false if example.nil? diff --git a/lib/rubocop/cop/rspec/instance_spy.rb b/lib/rubocop/cop/rspec/instance_spy.rb index 7b80c167a..96a2d407b 100644 --- a/lib/rubocop/cop/rspec/instance_spy.rb +++ b/lib/rubocop/cop/rspec/instance_spy.rb @@ -19,6 +19,8 @@ module RSpec # end # class InstanceSpy < Cop + extend AutoCorrector + MSG = 'Use `instance_spy` when you check your double '\ 'with `have_received`.' @@ -43,22 +45,26 @@ def on_block(node) null_double(node) do |var, receiver| have_received_usage(node) do |expected| - add_offense(receiver) if expected == var + next if expected != var + + add_offense(receiver) do |corrector| + autocorrect(corrector, receiver) + end end end end - def autocorrect(node) - lambda do |corrector| - replacement = 'instance_spy' - corrector.replace(node.loc.selector, replacement) + private - double_source_map = node.parent.loc - as_null_object_range = double_source_map - .dot - .join(double_source_map.selector) - corrector.remove(as_null_object_range) - end + def autocorrect(corrector, node) + replacement = 'instance_spy' + corrector.replace(node.loc.selector, replacement) + + double_source_map = node.parent.loc + as_null_object_range = double_source_map + .dot + .join(double_source_map.selector) + corrector.remove(as_null_object_range) end end end diff --git a/lib/rubocop/cop/rspec/invalid_predicate_matcher.rb b/lib/rubocop/cop/rspec/invalid_predicate_matcher.rb index c0944de78..011ea0027 100644 --- a/lib/rubocop/cop/rspec/invalid_predicate_matcher.rb +++ b/lib/rubocop/cop/rspec/invalid_predicate_matcher.rb @@ -24,7 +24,8 @@ class InvalidPredicateMatcher < Cop def on_send(node) invalid_predicate_matcher?(node) do |predicate| - add_offense(predicate) + add_offense(predicate, + message: format(MSG, matcher: predicate.method_name)) end end @@ -34,10 +35,6 @@ def predicate?(name) name = name.to_s name.start_with?('be_', 'have_') && name.end_with?('?') end - - def message(predicate) - format(MSG, matcher: predicate.method_name) - end end end end diff --git a/lib/rubocop/cop/rspec/it_behaves_like.rb b/lib/rubocop/cop/rspec/it_behaves_like.rb index 6da6f241d..bc219b573 100644 --- a/lib/rubocop/cop/rspec/it_behaves_like.rb +++ b/lib/rubocop/cop/rspec/it_behaves_like.rb @@ -19,6 +19,7 @@ module RSpec # # good # it_should_behave_like 'a foo' class ItBehavesLike < Cop + extend AutoCorrector include ConfigurableEnforcedStyle MSG = 'Prefer `%s` over `%s` when including '\ @@ -28,14 +29,12 @@ class ItBehavesLike < Cop def on_send(node) example_inclusion_offense(node, alternative_style) do - add_offense(node) + add_offense(node) do |corrector| + corrector.replace(node.loc.selector, style.to_s) + end end end - def autocorrect(node) - ->(corrector) { corrector.replace(node.loc.selector, style.to_s) } - end - private def message(_node) diff --git a/lib/rubocop/cop/rspec/leading_subject.rb b/lib/rubocop/cop/rspec/leading_subject.rb index 6176f5c58..9080e9a77 100644 --- a/lib/rubocop/cop/rspec/leading_subject.rb +++ b/lib/rubocop/cop/rspec/leading_subject.rb @@ -32,6 +32,8 @@ module RSpec # it { expect_something_else } # class LeadingSubject < Cop + extend AutoCorrector + MSG = 'Declare `subject` above any other `%s` declarations.' def on_block(node) @@ -43,27 +45,25 @@ def on_block(node) def check_previous_nodes(node) node.parent.each_child_node do |sibling| if offending?(sibling) - add_offense( - node, - message: format(MSG, offending: sibling.method_name) - ) + msg = format(MSG, offending: sibling.method_name) + add_offense(node, message: msg) do |corrector| + autocorrect(corrector, node) + end end break if offending?(sibling) || sibling.equal?(node) end end - def autocorrect(node) - lambda do |corrector| - first_node = find_first_offending_node(node) - RuboCop::RSpec::Corrector::MoveNode.new( - node, corrector, processed_source - ).move_before(first_node) - end - end - private + def autocorrect(corrector, node) + first_node = find_first_offending_node(node) + RuboCop::RSpec::Corrector::MoveNode.new( + node, corrector, processed_source + ).move_before(first_node) + end + def offending?(node) let?(node) || hook?(node) || example?(node) end diff --git a/lib/rubocop/cop/rspec/let_before_examples.rb b/lib/rubocop/cop/rspec/let_before_examples.rb index c41b60add..8df74f412 100644 --- a/lib/rubocop/cop/rspec/let_before_examples.rb +++ b/lib/rubocop/cop/rspec/let_before_examples.rb @@ -31,6 +31,8 @@ module RSpec # expect(some).to be # end class LetBeforeExamples < Cop + extend AutoCorrector + MSG = 'Move `let` before the examples in the group.' def_node_matcher :example_or_group?, <<-PATTERN @@ -46,15 +48,6 @@ def on_block(node) check_let_declarations(node.body) if multiline_block?(node.body) end - def autocorrect(node) - lambda do |corrector| - first_example = find_first_example(node.parent) - RuboCop::RSpec::Corrector::MoveNode.new( - node, corrector, processed_source - ).move_before(first_example) - end - end - private def multiline_block?(block) @@ -67,14 +60,23 @@ def check_let_declarations(node) node.each_child_node do |child| next if child.sibling_index < first_example.sibling_index + next unless let?(child) - add_offense(child) if let?(child) + add_offense(child) do |corrector| + autocorrect(corrector, child, first_example) + end end end def find_first_example(node) node.children.find { |sibling| example_or_group?(sibling) } end + + def autocorrect(corrector, node, first_example) + RuboCop::RSpec::Corrector::MoveNode.new( + node, corrector, processed_source + ).move_before(first_example) + end end end end diff --git a/lib/rubocop/cop/rspec/message_chain.rb b/lib/rubocop/cop/rspec/message_chain.rb index 0e9c020a2..c3fce664e 100644 --- a/lib/rubocop/cop/rspec/message_chain.rb +++ b/lib/rubocop/cop/rspec/message_chain.rb @@ -21,11 +21,12 @@ class MessageChain < Cop PATTERN def on_send(node) - message_chain(node) { add_offense(node, location: :selector) } - end - - def message(node) - format(MSG, method: node.method_name) + message_chain(node) do + add_offense( + node.loc.selector, + message: format(MSG, method: node.method_name) + ) + end end end end diff --git a/lib/rubocop/cop/rspec/message_expectation.rb b/lib/rubocop/cop/rspec/message_expectation.rb index 14b80996e..0a6e4e298 100644 --- a/lib/rubocop/cop/rspec/message_expectation.rb +++ b/lib/rubocop/cop/rspec/message_expectation.rb @@ -42,7 +42,7 @@ def on_send(node) return correct_style_detected if preferred_style?(match) message = format(MSG, style: style) - add_offense(match, location: :selector, message: message) do + add_offense(match.loc.selector, message: message) do opposite_style_detected end end diff --git a/lib/rubocop/cop/rspec/message_spies.rb b/lib/rubocop/cop/rspec/message_spies.rb index 3daf12c69..7f9f7fbc6 100644 --- a/lib/rubocop/cop/rspec/message_spies.rb +++ b/lib/rubocop/cop/rspec/message_spies.rb @@ -48,8 +48,7 @@ def on_send(node) return correct_style_detected if preferred_style?(message_matcher) add_offense( - message_matcher, - location: :selector, + message_matcher.loc.selector, message: error_message(receiver) ) { opposite_style_detected } end diff --git a/lib/rubocop/cop/rspec/multiple_subjects.rb b/lib/rubocop/cop/rspec/multiple_subjects.rb index f6d7d1c25..808d1f256 100644 --- a/lib/rubocop/cop/rspec/multiple_subjects.rb +++ b/lib/rubocop/cop/rspec/multiple_subjects.rb @@ -34,6 +34,7 @@ module RSpec # This is enough of an edge case that people can just move this to # a `before` hook on their own class MultipleSubjects < Cop + extend AutoCorrector include RangeHelp MSG = 'Do not set more than one subject per example group' @@ -44,38 +45,36 @@ def on_block(node) subjects = RuboCop::RSpec::ExampleGroup.new(node).subjects subjects[0...-1].each do |subject| - add_offense(subject) + add_offense(subject) do |corrector| + autocorrect(corrector, subject) + end end end - def autocorrect(node) - return unless node.method_name.equal?(:subject) # Ignore `subject!` + private + + def autocorrect(corrector, subject) + return unless subject.method_name.equal?(:subject) # Ignore `subject!` - if named_subject?(node) - rename_autocorrect(node) + if named_subject?(subject) + rename_autocorrect(corrector, subject) else - remove_autocorrect(node) + remove_autocorrect(corrector, subject) end end - private - def named_subject?(node) node.send_node.arguments? end - def rename_autocorrect(node) - lambda do |corrector| - corrector.replace(node.send_node.loc.selector, 'let') - end + def rename_autocorrect(corrector, node) + corrector.replace(node.send_node.loc.selector, 'let') end - def remove_autocorrect(node) - lambda do |corrector| - range = range_by_whole_lines(node.source_range, - include_final_newline: true) - corrector.remove(range) - end + def remove_autocorrect(corrector, node) + range = range_by_whole_lines(node.source_range, + include_final_newline: true) + corrector.remove(range) end end end diff --git a/lib/rubocop/cop/rspec/named_subject.rb b/lib/rubocop/cop/rspec/named_subject.rb index ac93b7124..efcbd93eb 100644 --- a/lib/rubocop/cop/rspec/named_subject.rb +++ b/lib/rubocop/cop/rspec/named_subject.rb @@ -62,7 +62,7 @@ def on_block(node) return if !rspec_block?(node) || ignored_shared_example?(node) subject_usage(node) do |subject_node| - add_offense(subject_node, location: :selector) + add_offense(subject_node.loc.selector) end end diff --git a/lib/rubocop/cop/rspec/not_to_not.rb b/lib/rubocop/cop/rspec/not_to_not.rb index 9d7959df1..726dbc201 100644 --- a/lib/rubocop/cop/rspec/not_to_not.rb +++ b/lib/rubocop/cop/rspec/not_to_not.rb @@ -16,6 +16,7 @@ module RSpec # expect(false).not_to be_true # end class NotToNot < Cop + extend AutoCorrector include ConfigurableEnforcedStyle MSG = 'Prefer `%s` over `%s`.' @@ -24,14 +25,12 @@ class NotToNot < Cop def on_send(node) not_to_not_offense(node, alternative_style) do - add_offense(node, location: :selector) + add_offense(node.loc.selector) do |corrector| + corrector.replace(node.loc.selector, style.to_s) + end end end - def autocorrect(node) - ->(corrector) { corrector.replace(node.loc.selector, style.to_s) } - end - private def message(_node) diff --git a/lib/rubocop/cop/rspec/predicate_matcher.rb b/lib/rubocop/cop/rspec/predicate_matcher.rb index 8c55458c2..504b72093 100644 --- a/lib/rubocop/cop/rspec/predicate_matcher.rb +++ b/lib/rubocop/cop/rspec/predicate_matcher.rb @@ -14,11 +14,14 @@ module InflectedHelper private def check_inflected(node) - predicate_in_actual?(node) do |predicate| - add_offense( - node, - message: message_inflected(predicate) - ) + predicate_in_actual?(node) do |predicate, to, matcher| + msg = message_inflected(predicate) + add_offense(node, message: msg) do |corrector| + remove_predicate(corrector, predicate) + corrector.replace(node.loc.selector, + true?(to, matcher) ? 'to' : 'not_to') + rewrite_matcher(corrector, predicate, matcher) + end end end @@ -76,17 +79,6 @@ def to_predicate_matcher(name) end # rubocop:enable Metrics/MethodLength - def autocorrect_inflected(node) - predicate_in_actual?(node) do |predicate, to, matcher| - lambda do |corrector| - remove_predicate(corrector, predicate) - corrector.replace(node.loc.selector, - true?(to, matcher) ? 'to' : 'not_to') - rewrite_matcher(corrector, predicate, matcher) - end - end - end - def remove_predicate(corrector, predicate) range = predicate.loc.dot.with( end_pos: predicate.loc.expression.end_pos @@ -123,7 +115,6 @@ def true?(to_symbol, matcher) end # A helper for `explicit` style - # rubocop:disable Metrics/ModuleLength module ExplicitHelper include RuboCop::RSpec::Language extend NodePattern::Macros @@ -143,22 +134,21 @@ def allowed_explicit_matchers end def check_explicit(node) # rubocop:disable Metrics/MethodLength - predicate_matcher_block?(node) do |_actual, matcher| - add_offense( - node, - message: message_explicit(matcher) - ) + predicate_matcher_block?(node) do |actual, matcher| + add_offense(node, message: message_explicit(matcher)) do |corrector| + to_node = node.send_node + corrector_explicit(corrector, to_node, actual, matcher, to_node) + end ignore_node(node.children.first) return end return if part_of_ignored_node?(node) - predicate_matcher?(node) do |_actual, matcher| - add_offense( - node, - message: message_explicit(matcher) - ) + predicate_matcher?(node) do |actual, matcher| + add_offense(node, message: message_explicit(matcher)) do |corrector| + corrector_explicit(corrector, node, actual, matcher, matcher) + end end end @@ -193,31 +183,11 @@ def message_explicit(matcher) matcher_name: matcher.method_name) end - def autocorrect_explicit(node) - autocorrect_explicit_send(node) || - autocorrect_explicit_block(node) - end - - def autocorrect_explicit_send(node) - predicate_matcher?(node) do |actual, matcher| - corrector_explicit(node, actual, matcher, matcher) - end - end - - def autocorrect_explicit_block(node) - predicate_matcher_block?(node) do |actual, matcher| - to_node = node.send_node - corrector_explicit(to_node, actual, matcher, to_node) - end - end - - def corrector_explicit(to_node, actual, matcher, block_child) - lambda do |corrector| - replacement_matcher = replacement_matcher(to_node) - corrector.replace(matcher.loc.expression, replacement_matcher) - move_predicate(corrector, actual, matcher, block_child) - corrector.replace(to_node.loc.selector, 'to') - end + def corrector_explicit(corrector, to_node, actual, matcher, block_child) + replacement_matcher = replacement_matcher(to_node) + corrector.replace(matcher.loc.expression, replacement_matcher) + move_predicate(corrector, actual, matcher, block_child) + corrector.replace(to_node.loc.selector, 'to') end def move_predicate(corrector, actual, matcher, block_child) @@ -261,7 +231,6 @@ def replacement_matcher(node) end end end - # rubocop:enable Metrics/ModuleLength # Prefer using predicate matcher over using predicate method directly. # @@ -301,6 +270,7 @@ def replacement_matcher(node) # # good - the above code is rewritten to it by this cop # expect(foo.something?).to be_truthy class PredicateMatcher < Cop + extend AutoCorrector include ConfigurableEnforcedStyle include InflectedHelper include ExplicitHelper diff --git a/lib/rubocop/cop/rspec/rails/http_status.rb b/lib/rubocop/cop/rspec/rails/http_status.rb index 32a4dbd1e..342675b54 100644 --- a/lib/rubocop/cop/rspec/rails/http_status.rb +++ b/lib/rubocop/cop/rspec/rails/http_status.rb @@ -31,6 +31,7 @@ module Rails # it { is_expected.to have_http_status :error } # class HttpStatus < Cop + extend AutoCorrector include ConfigurableEnforcedStyle def_node_matcher :http_status, <<-PATTERN @@ -42,14 +43,9 @@ def on_send(node) checker = checker_class.new(ast_node) return unless checker.offensive? - add_offense(checker.node, message: checker.message) - end - end - - def autocorrect(node) - lambda do |corrector| - checker = checker_class.new(node) - corrector.replace(node.loc.expression, checker.preferred_style) + add_offense(checker.node, message: checker.message) do |corrector| + corrector.replace(checker.node, checker.preferred_style) + end end end diff --git a/lib/rubocop/cop/rspec/receive_counts.rb b/lib/rubocop/cop/rspec/receive_counts.rb index 184932f3e..c806a3431 100644 --- a/lib/rubocop/cop/rspec/receive_counts.rb +++ b/lib/rubocop/cop/rspec/receive_counts.rb @@ -24,6 +24,8 @@ module RSpec # expect(foo).to receive(:bar).at_most(:twice).times # class ReceiveCounts < Cop + extend AutoCorrector + MSG = 'Use `%s` instead of `%s`.' def_node_matcher :receive_counts, <<-PATTERN @@ -38,27 +40,23 @@ def on_send(node) offending_range = range(node, offending_node) - add_offense( - offending_node, - message: message_for(offending_node, offending_range.source), - location: offending_range - ) + msg = message_for(offending_node, offending_range.source) + add_offense(offending_range, message: msg) do |corrector| + autocorrect(corrector, offending_node, offending_range) + end end end - def autocorrect(node) - lambda do |corrector| - replacement = matcher_for( - node.method_name, - node.first_argument.source.to_i - ) + private - original = range(node.parent, node) - corrector.replace(original, replacement) - end - end + def autocorrect(corrector, node, range) + replacement = matcher_for( + node.method_name, + node.first_argument.source.to_i + ) - private + corrector.replace(range, replacement) + end def message_for(node, source) alternative = matcher_for( diff --git a/lib/rubocop/cop/rspec/receive_never.rb b/lib/rubocop/cop/rspec/receive_never.rb index 1f8975e1e..109cca7b9 100644 --- a/lib/rubocop/cop/rspec/receive_never.rb +++ b/lib/rubocop/cop/rspec/receive_never.rb @@ -14,6 +14,7 @@ module RSpec # expect(foo).not_to receive(:bar) # class ReceiveNever < Cop + extend AutoCorrector MSG = 'Use `not_to receive` instead of `never`.' def_node_search :method_on_stub?, '(send nil? :receive ...)' @@ -21,18 +22,17 @@ class ReceiveNever < Cop def on_send(node) return unless node.method_name == :never && method_on_stub?(node) - add_offense( - node, - location: :selector - ) + add_offense(node.loc.selector) do |corrector| + autocorrect(corrector, node) + end end - def autocorrect(node) - lambda do |corrector| - corrector.replace(node.parent.loc.selector, 'not_to') - range = node.loc.dot.with(end_pos: node.loc.selector.end_pos) - corrector.remove(range) - end + private + + def autocorrect(corrector, node) + corrector.replace(node.parent.loc.selector, 'not_to') + range = node.loc.dot.with(end_pos: node.loc.selector.end_pos) + corrector.remove(range) end end end diff --git a/lib/rubocop/cop/rspec/return_from_stub.rb b/lib/rubocop/cop/rspec/return_from_stub.rb index c7f1b5d64..6484232ed 100644 --- a/lib/rubocop/cop/rspec/return_from_stub.rb +++ b/lib/rubocop/cop/rspec/return_from_stub.rb @@ -34,6 +34,7 @@ module RSpec # allow(Foo).to receive(:bar) { bar.baz } # class ReturnFromStub < Cop + extend AutoCorrector include ConfigurableEnforcedStyle MSG_AND_RETURN = 'Use `and_return` for static values.' @@ -59,24 +60,14 @@ def on_block(node) check_block_body(node) end - def autocorrect(node) - if style == :block - AndReturnCallCorrector.new(node) - else - BlockBodyCorrector.new(node) - end - end - private def check_and_return_call(node) and_return_value(node) do |and_return, args| unless dynamic?(args) - add_offense( - and_return, - location: :selector, - message: MSG_BLOCK - ) + add_offense(and_return.loc.selector, message: MSG_BLOCK) do |corr| + AndReturnCallCorrector.new(and_return).call(corr) + end end end end @@ -84,11 +75,9 @@ def check_and_return_call(node) def check_block_body(block) body = block.body unless dynamic?(body) # rubocop:disable Style/GuardClause - add_offense( - block, - location: :begin, - message: MSG_AND_RETURN - ) + add_offense(block.loc.begin, message: MSG_AND_RETURN) do |corrector| + BlockBodyCorrector.new(block).call(corrector) + end end end @@ -153,7 +142,7 @@ def call(corrector) return if heredoc? corrector.replace( - block.loc.expression, + block, "#{block.send_node.source}.and_return(#{body.source})" ) end diff --git a/lib/rubocop/cop/rspec/scattered_let.rb b/lib/rubocop/cop/rspec/scattered_let.rb index 07c88c6e1..02426458a 100644 --- a/lib/rubocop/cop/rspec/scattered_let.rb +++ b/lib/rubocop/cop/rspec/scattered_let.rb @@ -27,6 +27,8 @@ module RSpec # end # class ScatteredLet < Cop + extend AutoCorrector + MSG = 'Group all let/let! blocks in the example group together.' def on_block(node) @@ -35,15 +37,6 @@ def on_block(node) check_let_declarations(node.body) end - def autocorrect(node) - lambda do |corrector| - first_let = find_first_let(node.parent) - RuboCop::RSpec::Corrector::MoveNode.new( - node, corrector, processed_source - ).move_after(first_let) - end - end - private def check_let_declarations(body) @@ -53,7 +46,11 @@ def check_let_declarations(body) lets.each_with_index do |node, idx| next if node.sibling_index == first_let.sibling_index + idx - add_offense(node) + add_offense(node) do |corrector| + RuboCop::RSpec::Corrector::MoveNode.new( + node, corrector, processed_source + ).move_after(first_let) + end end end diff --git a/lib/rubocop/cop/rspec/shared_context.rb b/lib/rubocop/cop/rspec/shared_context.rb index d1815f0cd..6316ef25e 100644 --- a/lib/rubocop/cop/rspec/shared_context.rb +++ b/lib/rubocop/cop/rspec/shared_context.rb @@ -51,6 +51,8 @@ module RSpec # end # class SharedContext < Cop + extend AutoCorrector + MSG_EXAMPLES = "Use `shared_examples` when you don't "\ 'define context.' @@ -68,22 +70,14 @@ class SharedContext < Cop def on_block(node) context_with_only_examples(node) do - add_shared_item_offense(node.send_node, MSG_EXAMPLES) + add_offense(node.send_node, message: MSG_EXAMPLES) do |corrector| + corrector.replace(node.send_node.loc.selector, 'shared_examples') + end end examples_with_only_context(node) do - add_shared_item_offense(node.send_node, MSG_CONTEXT) - end - end - - def autocorrect(node) - lambda do |corrector| - context_with_only_examples(node.parent) do - corrector.replace(node.loc.selector, 'shared_examples') - end - - examples_with_only_context(node.parent) do - corrector.replace(node.loc.selector, 'shared_context') + add_offense(node.send_node, message: MSG_CONTEXT) do |corrector| + corrector.replace(node.send_node.loc.selector, 'shared_context') end end end @@ -97,13 +91,6 @@ def context_with_only_examples(node) def examples_with_only_context(node) shared_example(node) { yield if context?(node) && !examples?(node) } end - - def add_shared_item_offense(node, message) - add_offense( - node, - message: message - ) - end end end end diff --git a/lib/rubocop/cop/rspec/shared_examples.rb b/lib/rubocop/cop/rspec/shared_examples.rb index 522168a6f..eea12d7c7 100644 --- a/lib/rubocop/cop/rspec/shared_examples.rb +++ b/lib/rubocop/cop/rspec/shared_examples.rb @@ -21,6 +21,8 @@ module RSpec # include_examples 'foo bar baz' # class SharedExamples < Cop + extend AutoCorrector + def_node_matcher :shared_examples, (SharedGroups::ALL + Includes::ALL).send_pattern @@ -30,14 +32,9 @@ def on_send(node) next unless ast_node&.sym_type? checker = Checker.new(ast_node) - add_offense(checker.node, message: checker.message) - end - end - - def autocorrect(node) - lambda do |corrector| - checker = Checker.new(node) - corrector.replace(node.loc.expression, checker.preferred_style) + add_offense(checker.node, message: checker.message) do |corrector| + corrector.replace(checker.node, checker.preferred_style) + end end end diff --git a/lib/rubocop/cop/rspec/single_argument_message_chain.rb b/lib/rubocop/cop/rspec/single_argument_message_chain.rb index 7c4506d33..b8c8a725b 100644 --- a/lib/rubocop/cop/rspec/single_argument_message_chain.rb +++ b/lib/rubocop/cop/rspec/single_argument_message_chain.rb @@ -17,6 +17,8 @@ module RSpec # allow(foo).to receive("bar.baz") # class SingleArgumentMessageChain < Cop + extend AutoCorrector + MSG = 'Use `%s` instead of calling '\ '`%s` with a single argument.' @@ -30,22 +32,23 @@ def on_send(node) message_chain(node) do |arg| return if valid_usage?(arg) - add_offense(node, location: :selector) - end - end + method = node.method_name + msg = format(MSG, recommended: replacement(method), called: method) - def autocorrect(node) - lambda do |corrector| - corrector.replace(node.loc.selector, replacement(node.method_name)) - message_chain(node) do |arg| - autocorrect_hash_arg(corrector, arg) if single_key_hash?(arg) - autocorrect_array_arg(corrector, arg) if arg.array_type? + add_offense(node.loc.selector, message: msg) do |corrector| + autocorrect(corrector, node, method, arg) end end end private + def autocorrect(corrector, node, method, arg) + corrector.replace(node.loc.selector, replacement(method)) + autocorrect_hash_arg(corrector, arg) if single_key_hash?(arg) + autocorrect_array_arg(corrector, arg) if arg.array_type? + end + def valid_usage?(node) return true unless node.literal? || node.array_type? @@ -63,7 +66,7 @@ def single_element_array?(node) def autocorrect_hash_arg(corrector, arg) key, value = *arg.children.first - corrector.replace(arg.loc.expression, key_to_arg(key)) + corrector.replace(arg, key_to_arg(key)) corrector.insert_after(arg.parent.loc.end, ".and_return(#{value.source})") end @@ -71,7 +74,7 @@ def autocorrect_hash_arg(corrector, arg) def autocorrect_array_arg(corrector, arg) value = arg.children.first - corrector.replace(arg.loc.expression, value.source) + corrector.replace(arg, value.source) end def key_to_arg(node) @@ -82,12 +85,6 @@ def key_to_arg(node) def replacement(method) method.equal?(:receive_message_chain) ? 'receive' : 'stub' end - - def message(node) - method = node.method_name - - format(MSG, recommended: replacement(method), called: method) - end end end end diff --git a/lib/rubocop/cop/rspec/yield.rb b/lib/rubocop/cop/rspec/yield.rb index 1d931c978..22d6839e1 100644 --- a/lib/rubocop/cop/rspec/yield.rb +++ b/lib/rubocop/cop/rspec/yield.rb @@ -12,6 +12,7 @@ module RSpec # # good # expect(foo).to be(:bar).and_yield(1) class Yield < Cop + extend AutoCorrector include RangeHelp MSG = 'Use `.and_yield`.' @@ -27,22 +28,24 @@ def on_block(node) block_arg(node.arguments) do |block| if calling_block?(node.body, block) - add_offense(node, location: block_range(node)) - end - end - end + range = block_range(node) - def autocorrect(node) - lambda do |corrector| - node_range = range_with_surrounding_space( - range: block_range(node), side: :left - ) - corrector.replace(node_range, generate_replacement(node.body)) + add_offense(range) do |corrector| + autocorrect(corrector, node, range) + end + end end end private + def autocorrect(corrector, node, range) + corrector.replace( + range_with_surrounding_space(range: range, side: :left), + generate_replacement(node.body) + ) + end + def calling_block?(node, block) if node.begin_type? node.each_child_node.all? { |child| block_call?(child, block) } diff --git a/lib/rubocop/rspec/blank_line_separation.rb b/lib/rubocop/rspec/blank_line_separation.rb index 6b9ce28aa..45375e7c5 100644 --- a/lib/rubocop/rspec/blank_line_separation.rb +++ b/lib/rubocop/rspec/blank_line_separation.rb @@ -32,14 +32,6 @@ def last_child?(node) node.equal?(node.parent.children.last) end - - def autocorrect(node) - lambda do |corrector| - missing_separating_line(node) do |location| - corrector.insert_after(location.end, "\n") - end - end - end end end end diff --git a/rubocop-rspec.gemspec b/rubocop-rspec.gemspec index 48af29b11..fb0299e74 100644 --- a/rubocop-rspec.gemspec +++ b/rubocop-rspec.gemspec @@ -36,7 +36,7 @@ Gem::Specification.new do |spec| 'documentation_uri' => 'https://rubocop-rspec.readthedocs.io/' } - spec.add_runtime_dependency 'rubocop', '>= 0.68.1' + spec.add_runtime_dependency 'rubocop', '>= 0.87.0' spec.add_development_dependency 'rack' spec.add_development_dependency 'rake' diff --git a/spec/rubocop/cop/rspec/expect_actual_spec.rb b/spec/rubocop/cop/rspec/expect_actual_spec.rb index fdb5c8b1d..a2062f066 100644 --- a/spec/rubocop/cop/rspec/expect_actual_spec.rb +++ b/spec/rubocop/cop/rspec/expect_actual_spec.rb @@ -308,12 +308,10 @@ let(:cop_config) { {} } it 'ignores rspec-rails routing specs' do - inspect_source( + expect_no_offenses( 'expect(get: "/foo").to be_routeable', 'spec/routing/foo_spec.rb' ) - - expect(cop.offenses).to be_empty end end end diff --git a/tasks/cops_documentation.rake b/tasks/cops_documentation.rake index 8b06365fb..2f0ced818 100644 --- a/tasks/cops_documentation.rake +++ b/tasks/cops_documentation.rake @@ -40,7 +40,7 @@ task generate_cops_documentation: :yard_for_generate_documentation do ] config = config.for_cop(cop) safe_auto_correct = config.fetch('SafeAutoCorrect', true) - autocorrect = if cop.new.support_autocorrect? + autocorrect = if cop.support_autocorrect? "Yes #{'(Unsafe)' unless safe_auto_correct}" else 'No'