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

Inherits:
Cop
  • Object
show all
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.'
LOGIC_JUMP_KEYWORDS =
%i[break fail next raise
return throw yield].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 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, #disable_uncorrectable, #duplicate_location?, #excluded_file?, #find_location, #highlights, inherited, #initialize, #join_force?, lint?, match?, #message, #messages, #parse, qualified_cop_name, #reason_to_not_correct, #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?, #correctable?, #disable_offense, #disable_uncorrectable?, #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



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

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, '&')
    handle_comments(corrector, method_call)

    add_safe_nav_to_all_methods_in_chain(corrector, method_call, body)
  end
end

#check_node(node) ⇒ Object



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

def check_node(node)
  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



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

def on_and(node)
  check_node(node)
end

#on_if(node) ⇒ Object



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

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)


110
111
112
# File 'lib/rubocop/cop/style/safe_navigation.rb', line 110

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