From c1773e1605e1e128f61fc0fa4d40edf0d8d920f4 Mon Sep 17 00:00:00 2001 From: Koichi ITO Date: Mon, 31 May 2021 16:22:33 +0900 Subject: [PATCH] [Fix #9839] Add `AllowedReceivers` option for `Style/HashEachMethods` Fixes #9839. This PR adds `AllowedReceivers` option for `Style/HashEachMethods`. `Style/HashEachMethods` cop is already marked as unsafe. This PR can use allow list to rule out false positives. --- ...vers_option_for_style_hash_each_methods.md | 1 + config/default.yml | 4 +++- lib/rubocop/cop/style/hash_each_methods.rb | 19 ++++++++++++++- .../cop/style/hash_each_methods_spec.rb | 24 +++++++++++++++++++ 4 files changed, 46 insertions(+), 2 deletions(-) create mode 100644 changelog/new_add_allowed_receivers_option_for_style_hash_each_methods.md diff --git a/changelog/new_add_allowed_receivers_option_for_style_hash_each_methods.md b/changelog/new_add_allowed_receivers_option_for_style_hash_each_methods.md new file mode 100644 index 00000000000..aaf21355232 --- /dev/null +++ b/changelog/new_add_allowed_receivers_option_for_style_hash_each_methods.md @@ -0,0 +1 @@ +* [#9840](https://github.com/rubocop/rubocop/issues/9840): Adds `AllowedReceivers` option for `Style/HashEachMethods`. ([@koic][]) diff --git a/config/default.yml b/config/default.yml index f7e1d07640d..3f1a2f4ed0b 100644 --- a/config/default.yml +++ b/config/default.yml @@ -3497,8 +3497,10 @@ Style/HashEachMethods: Description: 'Use Hash#each_key and Hash#each_value.' StyleGuide: '#hash-each' Enabled: true - VersionAdded: '0.80' Safe: false + VersionAdded: '0.80' + VersionChanged: <> + AllowedReceivers: [] Style/HashExcept: Description: >- diff --git a/lib/rubocop/cop/style/hash_each_methods.rb b/lib/rubocop/cop/style/hash_each_methods.rb index d5ed17abfe1..86755fce114 100644 --- a/lib/rubocop/cop/style/hash_each_methods.rb +++ b/lib/rubocop/cop/style/hash_each_methods.rb @@ -17,6 +17,11 @@ module Style # # good # hash.each_key { |k| p k } # hash.each_value { |v| p v } + # + # @example AllowedReceivers: ['execute'] + # # good + # execute(sql).keys.each { |v| p v } + # execute(sql).values.each { |v| p v } class HashEachMethods < Base include Lint::UnusedArgument extend AutoCorrector @@ -36,7 +41,9 @@ def on_block(node) def register_kv_offense(node) kv_each(node) do |target, method| - return unless target.receiver.receiver + parent_receiver = target.receiver.receiver + return unless parent_receiver + return if allowed_receiver?(parent_receiver) msg = format(message, prefer: "each_#{method[0..-2]}", current: "#{method}.each") @@ -80,6 +87,16 @@ def correct_args(node, corrector) def kv_range(outer_node) outer_node.receiver.loc.selector.join(outer_node.loc.selector) end + + def allowed_receiver?(receiver) + receiver_name = receiver.send_type? ? receiver.method_name.to_s : receiver.source + + allowed_receivers.include?(receiver_name) + end + + def allowed_receivers + cop_config.fetch('AllowedReceivers', []) + end end end end diff --git a/spec/rubocop/cop/style/hash_each_methods_spec.rb b/spec/rubocop/cop/style/hash_each_methods_spec.rb index d2bf8785c33..9cb00b7eca7 100644 --- a/spec/rubocop/cop/style/hash_each_methods_spec.rb +++ b/spec/rubocop/cop/style/hash_each_methods_spec.rb @@ -87,5 +87,29 @@ expect_no_offenses('each_value { |v| p v }') end end + + context "when `AllowedReceivers: ['execute']`" do + let(:cop_config) { { 'AllowedReceivers' => ['execute'] } } + + it 'does not register an offense when receiver is `execute` method' do + expect_no_offenses(<<~RUBY) + execute(sql).values.each { |v| p v } + RUBY + end + + it 'does not register an offense when receiver is `execute` variable' do + expect_no_offenses(<<~RUBY) + execute = do_something(argument) + execute.values.each { |v| p v } + RUBY + end + + it 'registers an offense when receiver is not allowed name' do + expect_offense(<<~RUBY) + do_something(arg).values.each { |v| p v } + ^^^^^^^^^^^ Use `each_value` instead of `values.each`. + RUBY + end + end end end