diff --git a/changelog/fix_false_positive_for_hash_except.md b/changelog/fix_false_positive_for_hash_except.md new file mode 100644 index 00000000000..2fe5d0c8171 --- /dev/null +++ b/changelog/fix_false_positive_for_hash_except.md @@ -0,0 +1 @@ +* [#11868](https://github.com/rubocop/rubocop/issues/11868): Fix a false positive for `Style/HashExcept` when method's receiver/argument is not the same as block key argument. ([@fatkodima][]) diff --git a/lib/rubocop/cop/style/hash_except.rb b/lib/rubocop/cop/style/hash_except.rb index f18cf3ef2ca..ead2a6c5adf 100644 --- a/lib/rubocop/cop/style/hash_except.rb +++ b/lib/rubocop/cop/style/hash_except.rb @@ -45,13 +45,13 @@ class HashExcept < Base (block (send _ _) (args - (arg _) + $(arg _) (arg _)) { - (send + $(send _ {:== :!= :eql? :include?} _) (send - (send + $(send _ {:== :!= :eql? :include?} _) :!) }) PATTERN @@ -61,13 +61,13 @@ class HashExcept < Base (block (send _ _) (args - (arg _) + $(arg _) (arg _)) { - (send + $(send _ {:== :!= :eql? :in? :include? :exclude?} _) (send - (send + $(send _ {:== :!= :eql? :in? :include? :exclude?} _) :!) }) PATTERN @@ -89,13 +89,24 @@ def on_send(node) private + # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity def bad_method?(block) if active_support_extensions_enabled? - bad_method_with_active_support?(block) + bad_method_with_active_support?(block) do |key_arg, send_node| + if send_node.method?(:in?) && send_node.receiver&.source != key_arg.source + return false + end + return true if !send_node.method?(:include?) && !send_node.method?(:exclude?) + + send_node.first_argument&.source == key_arg.source + end else - bad_method_with_poro?(block) + bad_method_with_poro?(block) do |key_arg, send_node| + !send_node.method?(:include?) || send_node.first_argument&.source == key_arg.source + end end end + # rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity def semantically_except_method?(send, block) body = block.body diff --git a/spec/rubocop/cop/style/hash_except_spec.rb b/spec/rubocop/cop/style/hash_except_spec.rb index 328512c5d06..dd36a7a89c8 100644 --- a/spec/rubocop/cop/style/hash_except_spec.rb +++ b/spec/rubocop/cop/style/hash_except_spec.rb @@ -172,6 +172,18 @@ {foo: 1, bar: 2, baz: 3}.reject { |k, v| ![1, 2].include?(v) } RUBY end + + it 'does not register an offense when using `reject` and calling `include?` method on a key' do + expect_no_offenses(<<~RUBY) + {foo: 1, bar: 2, baz: 3}.reject { |k, v| k.include?('oo') } + RUBY + end + + it 'does not register an offense when using `reject` and calling `!include?` method on a key' do + expect_no_offenses(<<~RUBY) + {foo: 1, bar: 2, baz: 3}.reject { |k, v| !k.include?('oo') } + RUBY + end end context 'using `exclude?`' do @@ -180,6 +192,18 @@ {foo: 1, bar: 2, baz: 3}.reject { |k, v| !%i[foo bar].exclude?(k) } RUBY end + + it 'does not register an offense when using `reject` and calling `exclude?` method on a key' do + expect_no_offenses(<<~RUBY) + {foo: 1, bar: 2, baz: 3}.reject { |k, v| k.exclude?('oo') } + RUBY + end + + it 'does not register an offense when using `reject` and calling `!exclude?` method on a key' do + expect_no_offenses(<<~RUBY) + {foo: 1, bar: 2, baz: 3}.reject { |k, v| !k.exclude?('oo') } + RUBY + end end it 'does not register an offense when using `reject` and other than comparison by string and symbol using `==`' do @@ -457,6 +481,18 @@ {foo: 1, bar: 2, baz: 3}.except(*array) RUBY end + + it 'does not register an offense when using `reject` and calling `include?` method on a key' do + expect_no_offenses(<<~RUBY) + {foo: 1, bar: 2, baz: 3}.reject { |k, v| k.include?('oo') } + RUBY + end + + it 'does not register an offense when using `reject` and calling `!include?` method on a key' do + expect_no_offenses(<<~RUBY) + {foo: 1, bar: 2, baz: 3}.reject { |k, v| !k.include?('oo') } + RUBY + end end context 'using `exclude?`' do @@ -544,6 +580,18 @@ {foo: 1, bar: 2, baz: 3}.reject { |k, v| ![1, 2].exclude?(v) } RUBY end + + it 'does not register an offense when using `reject` and calling `exclude?` method on a key' do + expect_no_offenses(<<~RUBY) + {foo: 1, bar: 2, baz: 3}.reject { |k, v| k.exclude?('oo') } + RUBY + end + + it 'does not register an offense when using `reject` and calling `!exclude?` method on a key' do + expect_no_offenses(<<~RUBY) + {foo: 1, bar: 2, baz: 3}.reject { |k, v| !k.exclude?('oo') } + RUBY + end end it 'does not register an offense when using `reject` and other than comparison by string and symbol using `==`' do