diff --git a/changelog/fix_false_negatives_for_lint_redundant_safe_navigation.md b/changelog/fix_false_negatives_for_lint_redundant_safe_navigation.md new file mode 100644 index 000000000000..08e4f28ab123 --- /dev/null +++ b/changelog/fix_false_negatives_for_lint_redundant_safe_navigation.md @@ -0,0 +1 @@ +* [#12673](https://github.com/rubocop/rubocop/pull/12673): Fix false negatives for `Lint/RedundantSafeNavigation` when using safe navigation operator for literal receiver. ([@koic][]) diff --git a/lib/rubocop/cop/lint/redundant_safe_navigation.rb b/lib/rubocop/cop/lint/redundant_safe_navigation.rb index cd51ed01e691..be3f0130c7a3 100644 --- a/lib/rubocop/cop/lint/redundant_safe_navigation.rb +++ b/lib/rubocop/cop/lint/redundant_safe_navigation.rb @@ -5,7 +5,8 @@ module Cop module Lint # Checks for redundant safe navigation calls. # Use cases where a constant, named in camel case for classes and modules is `nil` are rare, - # and an offense is not detected when the receiver is a snake case constant. + # and an offense is not detected when the receiver is a constant. The detection also applies + # to literal receivers, except for `nil`. # # For all receivers, the `instance_of?`, `kind_of?`, `is_a?`, `eql?`, `respond_to?`, # and `equal?` methods are checked by default. @@ -105,7 +106,7 @@ class RedundantSafeNavigation < Base # rubocop:disable Metrics/AbcSize def on_csend(node) - unless node.receiver.const_type? && !node.receiver.source.match?(SNAKE_CASE) + unless assume_receiver_instance_exists?(node.receiver) return unless check?(node) && allowed_method?(node.method_name) return if respond_to_nil_specific_method?(node) end @@ -131,6 +132,12 @@ def on_or(node) private + def assume_receiver_instance_exists?(receiver) + return true if receiver.const_type? && !receiver.source.match?(SNAKE_CASE) + + receiver.literal? && !receiver.nil_type? + end + def check?(node) parent = node.parent return false unless parent diff --git a/spec/rubocop/cop/lint/redundant_safe_navigation_spec.rb b/spec/rubocop/cop/lint/redundant_safe_navigation_spec.rb index aa0b612ff685..8e2c7aa5cc6f 100644 --- a/spec/rubocop/cop/lint/redundant_safe_navigation_spec.rb +++ b/spec/rubocop/cop/lint/redundant_safe_navigation_spec.rb @@ -58,6 +58,56 @@ RUBY end + it 'registers an offense and corrects when `&.` is used for string literals' do + expect_offense(<<~RUBY) + '2012-03-02 16:05:37'&.to_time + ^^^^^^^^^ Redundant safe navigation detected. + RUBY + + expect_correction(<<~RUBY) + '2012-03-02 16:05:37'.to_time + RUBY + end + + it 'registers an offense and corrects when `&.` is used for integer literals' do + expect_offense(<<~RUBY) + 42&.minutes + ^^^^^^^^^ Redundant safe navigation detected. + RUBY + + expect_correction(<<~RUBY) + 42.minutes + RUBY + end + + it 'registers an offense and corrects when `&.` is used for array literals' do + expect_offense(<<~RUBY) + [1, 2, 3]&.join(', ') + ^^^^^^^^^^^^ Redundant safe navigation detected. + RUBY + + expect_correction(<<~RUBY) + [1, 2, 3].join(', ') + RUBY + end + + it 'registers an offense and corrects when `&.` is used for hash literals' do + expect_offense(<<~RUBY) + {k: :v}&.count + ^^^^^^^ Redundant safe navigation detected. + RUBY + + expect_correction(<<~RUBY) + {k: :v}.count + RUBY + end + + it 'does not register an offense and corrects when `&.` is used for `nil` literal' do + expect_no_offenses(<<~RUBY) + nil&.to_i + RUBY + end + %i[while until].each do |loop_type| it 'registers an offense and corrects when `&.` is used inside `#{loop_type}` condition' do expect_offense(<<~RUBY, loop_type: loop_type)