class RuboCop::Cop::Lint::RedundantSafeNavigation
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 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. These are customizable with `AllowedMethods` option.
The ‘AllowedMethods` option specifies nil-safe methods, in other words, it is a method that is allowed to skip safe navigation. Note that the `AllowedMethod` option is not an option that specifies methods for which to suppress (allow) this cop’s check.
In the example below, the safe navigation operator (‘&.`) is unnecessary because `NilClass` has methods like `respond_to?` and `is_a?`.
@safety
This cop is unsafe, because autocorrection can change the return type of the expression. An offending expression that previously could return `nil` will be autocorrected to never return `nil`.
@example
# bad CamelCaseConst&.do_something # bad do_something if attrs&.respond_to?(:[]) # good do_something if attrs.respond_to?(:[]) # bad while node&.is_a?(BeginNode) node = node.parent end # good CamelCaseConst.do_something # good while node.is_a?(BeginNode) node = node.parent end # good - without `&.` this will always return `true` foo&.respond_to?(:to_a) # bad - for `nil`s conversion methods return default values for the type foo&.to_h || {} foo&.to_h { |k, v| [k, v] } || {} foo&.to_a || [] foo&.to_i || 0 foo&.to_f || 0.0 foo&.to_s || '' # good foo.to_h foo.to_h { |k, v| [k, v] } foo.to_a foo.to_i foo.to_f foo.to_s
@example AllowedMethods: [nil_safe_method]
# bad do_something if attrs&.nil_safe_method(:[]) # good do_something if attrs.nil_safe_method(:[]) do_something if attrs&.not_nil_safe_method(:[])
Constants
- MSG
- MSG_LITERAL
- NIL_SPECIFIC_METHODS
- SNAKE_CASE
Public Instance Methods
rubocop:disable Metrics/AbcSize
# File lib/rubocop/cop/lint/redundant_safe_navigation.rb, line 107 def on_csend(node) 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 range = node.loc.dot add_offense(range) { |corrector| corrector.replace(range, '.') } end
# File lib/rubocop/cop/lint/redundant_safe_navigation.rb, line 117 def on_or(node) conversion_with_default?(node) do |send_node| range = send_node.loc.dot.begin.join(node.source_range.end) add_offense(range, message: MSG_LITERAL) do |corrector| corrector.replace(send_node.loc.dot, '.') range_with_default = node.lhs.source_range.end.begin.join(node.source_range.end) corrector.remove(range_with_default) end end end
Private Instance Methods
rubocop:enable Metrics/AbcSize
# File lib/rubocop/cop/lint/redundant_safe_navigation.rb, line 133 def assume_receiver_instance_exists?(receiver) return true if receiver.const_type? && !receiver.short_name.match?(SNAKE_CASE) receiver.literal? && !receiver.nil_type? end
# File lib/rubocop/cop/lint/redundant_safe_navigation.rb, line 139 def check?(node) parent = node.parent return false unless parent condition?(parent, node) || parent.and_type? || parent.or_type? || (parent.send_type? && parent.negation_method?) end
# File lib/rubocop/cop/lint/redundant_safe_navigation.rb, line 149 def condition?(parent, node) (parent.conditional? || parent.post_condition_loop?) && parent.condition == node end