diff --git a/changelog/fix_a_false_positive_for_lint_void.md b/changelog/fix_a_false_positive_for_lint_void.md new file mode 100644 index 000000000000..fa8877189365 --- /dev/null +++ b/changelog/fix_a_false_positive_for_lint_void.md @@ -0,0 +1 @@ +* [#12274](https://github.com/rubocop/rubocop/issues/12274): Fix a false positive for `Lint/Void` when `each`'s receiver is an object of `Enumerator` to which `filter` has been applied. ([@koic][]) diff --git a/lib/rubocop/cop/lint/void.rb b/lib/rubocop/cop/lint/void.rb index 60b2838b7808..2048559ca927 100644 --- a/lib/rubocop/cop/lint/void.rb +++ b/lib/rubocop/cop/lint/void.rb @@ -6,6 +6,16 @@ module Lint # Checks for operators, variables, literals, lambda, proc and nonmutating # methods used in void context. # + # `each` blocks are allowed to prevent false positives. + # For example, the expression inside the `each` block below. + # It's not void, especially when the receiver is an `Enumerator`: + # + # [source,ruby] + # ---- + # enumerator = [1, 2, 3].filter + # enumerator.each { |item| item >= 2 } #=> [2, 3] + # ---- + # # @example CheckForMethodsWithNoSideEffects: false (default) # # bad # def some_method @@ -72,6 +82,7 @@ def on_block(node) return unless node.body && !node.body.begin_type? return unless in_void_context?(node.body) + check_void_op(node.body) { node.method?(:each) } check_expression(node.body) end @@ -87,11 +98,13 @@ def on_begin(node) def check_begin(node) expressions = *node expressions.pop unless in_void_context?(node) - expressions.each { |expr| check_expression(expr) } + expressions.each do |expr| + check_void_op(expr) + check_expression(expr) + end end def check_expression(expr) - check_void_op(expr) check_literal(expr) check_var(expr) check_self(expr) @@ -101,8 +114,9 @@ def check_expression(expr) check_nonmutating(expr) end - def check_void_op(node) + def check_void_op(node, &block) return unless node.send_type? && OPERATORS.include?(node.method_name) + return if block && yield(node) add_offense(node.loc.selector, message: format(OP_MSG, op: node.method_name)) do |corrector| diff --git a/spec/rubocop/cop/lint/void_spec.rb b/spec/rubocop/cop/lint/void_spec.rb index 2ed71e7c01d5..b85678dd5a67 100644 --- a/spec/rubocop/cop/lint/void_spec.rb +++ b/spec/rubocop/cop/lint/void_spec.rb @@ -451,6 +451,16 @@ def foo=(rhs) RUBY end + it 'does not register `#each` block with conditional expression' do + expect_no_offenses(<<~RUBY) + enumerator_as_filter.each do |item| + # The `filter` method is used to filter for matches with `42`. + # In this case, it's not void. + item == 42 + end + RUBY + end + context 'Ruby 2.7', :ruby27 do it 'registers two offenses for void literals in `#tap` method' do expect_offense(<<~RUBY)