Class: RuboCop::Cop::Style::SafeNavigation

Inherits:
Cop
  • Object
show all
Extended by:
TargetRubyVersion
Includes:
NilMethods, RangeHelp
Defined in:
lib/rubocop/cop/style/safe_navigation.rb

Overview

This cop transforms usages of a method call safeguarded by a non ‘nil` check for the variable whose method is being called to safe navigation (`&.`). If there is a method chain, all of the methods in the chain need to be checked for safety, and all of the methods will need to be changed to use safe navigation. We have limited the cop to not register an offense for method chains that exceed 2 methods.

Configuration option: ConvertCodeThatCanStartToReturnNil The default for this is ‘false`. When configured to `true`, this will check for code in the format `!foo.nil? && foo.bar`. As it is written, the return of this code is limited to `false` and whatever the return of the method is. If this is converted to safe navigation, `foo&.bar` can start returning `nil` as well as what the method returns.

Examples:

# bad
foo.bar if foo
foo.bar.baz if foo
foo.bar(param1, param2) if foo
foo.bar { |e| e.something } if foo
foo.bar(param) { |e| e.something } if foo

foo.bar if !foo.nil?
foo.bar unless !foo
foo.bar unless foo.nil?

foo && foo.bar
foo && foo.bar.baz
foo && foo.bar(param1, param2)
foo && foo.bar { |e| e.something }
foo && foo.bar(param) { |e| e.something }

# good
foo&.bar
foo&.bar&.baz
foo&.bar(param1, param2)
foo&.bar { |e| e.something }
foo&.bar(param) { |e| e.something }
foo && foo.bar.baz.qux # method chain with more than 2 methods
foo && foo.nil? # method that `nil` responds to

# Method calls that do not use `.`
foo && foo < bar
foo < bar if foo

# This could start returning `nil` as well as the return of the method
foo.nil? || foo.bar
!foo || foo.bar

# Methods that are used on assignment, arithmetic operation or
# comparison should not be converted to use safe navigation
foo.baz = bar if foo
foo.baz + bar if foo
foo.bar > 2 if foo

Constant Summary collapse

MSG =
'Use safe navigation (`&.`) instead of checking if an object ' \
'exists before calling the method.'.freeze

Constants included from Util

Util::LITERAL_REGEX

Instance Attribute Summary

Attributes inherited from Cop

#config, #corrections, #offenses, #processed_source

Instance Method Summary collapse

Methods included from TargetRubyVersion

minimum_target_ruby_version, support_target_ruby_version?

Methods inherited from Cop

#add_offense, all, autocorrect_incompatible_with, badge, #config_to_allow_offenses, #config_to_allow_offenses=, #cop_config, cop_name, #cop_name, #correct, department, #duplicate_location?, #excluded_file?, #find_location, #highlights, inherited, #initialize, #join_force?, lint?, match?, #message, #messages, non_rails, #parse, qualified_cop_name, #relevant_file?, #target_rails_version, #target_ruby_version

Methods included from AST::Sexp

#s

Methods included from NodePattern::Macros

#def_node_matcher, #def_node_search, #node_search, #node_search_all, #node_search_body, #node_search_first

Methods included from AutocorrectLogic

#autocorrect?, #autocorrect_enabled?, #autocorrect_requested?, #support_autocorrect?

Methods included from IgnoredNode

#ignore_node, #ignored_node?, #part_of_ignored_node?

Methods included from Util

begins_its_line?, comment_line?, double_quotes_required?, escape_string, first_part_of_call_chain, interpret_string_escapes, line_range, needs_escaping?, on_node, parentheses?, same_line?, to_string_literal, to_supported_styles, tokens, trim_string_interporation_escape_character

Methods included from PathUtil

absolute?, chdir, hidden_dir?, hidden_file_in_not_hidden_dir?, match_path?, pwd, relative_path, reset_pwd, smart_path

Constructor Details

This class inherits a constructor from RuboCop::Cop::Cop

Instance Method Details

#autocorrect(node) ⇒ Object



117
118
119
120
121
122
123
124
125
126
127
128
129
# File 'lib/rubocop/cop/style/safe_navigation.rb', line 117

def autocorrect(node)
  _check, body, = node.node_parts
  _checked_variable, matching_receiver, = extract_parts(node)
  method_call, = matching_receiver.parent

  lambda do |corrector|
    corrector.remove(begin_range(node, body))
    corrector.remove(end_range(node, body))
    corrector.insert_before(method_call.loc.dot, '&')

    add_safe_nav_to_all_methods_in_chain(corrector, method_call, body)
  end
end

#check_node(node) ⇒ Object



99
100
101
102
103
104
105
106
107
108
109
110
111
# File 'lib/rubocop/cop/style/safe_navigation.rb', line 99

def check_node(node)
  return if target_ruby_version < 2.3

  checked_variable, receiver, method_chain, method = extract_parts(node)
  return unless receiver == checked_variable
  return if use_var_only_in_unless_modifier?(node, checked_variable)
  # method is already a method call so this is actually checking for a
  # chain greater than 2
  return if chain_size(method_chain, method) > 1
  return if unsafe_method_used?(method_chain, method)

  add_offense(node)
end

#on_and(node) ⇒ Object



95
96
97
# File 'lib/rubocop/cop/style/safe_navigation.rb', line 95

def on_and(node)
  check_node(node)
end

#on_if(node) ⇒ Object



89
90
91
92
93
# File 'lib/rubocop/cop/style/safe_navigation.rb', line 89

def on_if(node)
  return if allowed_if_condition?(node)

  check_node(node)
end

#use_var_only_in_unless_modifier?(node, variable) ⇒ Boolean

Returns:

  • (Boolean)


113
114
115
# File 'lib/rubocop/cop/style/safe_navigation.rb', line 113

def use_var_only_in_unless_modifier?(node, variable)
  node.if_type? && node.unless? && !method_called?(variable)
end