class RuboCop::Cop::Lint::SafeNavigationChain
x&.foo || bar
x&.foo&.bar
# good
x&.foo[bar]
x&.foo + bar
x&.foo.bar
# bad
@example
This cop checks for the problem outlined above.
safe navigation operator after a safe navigation operator.
navigation operator, it raises NoMethodError. We should use a
nil. If you chain an ordinary method call after a safe
The safe navigation operator returns nil if the receiver is
def add_safe_navigation_operator(offense_range:, send_node:)
-
(String)
-
Parameters:
-
send_node
(RuboCop::AST::SendNode
) -- -
offense_range
(Parser::Source::Range
) --
def add_safe_navigation_operator(offense_range:, send_node:) source = if brackets?(send_node) format( '%<method_name>s(%<arguments>s)%<method_chain>s', arguments: send_node.arguments.map(&:source).join(', '), method_name: send_node.method_name, method_chain: send_node.source_range.end.join(send_node.source_range.end).source ) else offense_range.source end source.prepend('.') unless source.start_with?('.') source.prepend('&') end
def autocorrect(corrector, offense_range:, send_node:)
-
send_node
(RuboCop::AST::SendNode
) -- -
offense_range
(Parser::Source::Range
) -- -
corrector
(RuboCop::Cop::Corrector
) --
def autocorrect(corrector, offense_range:, send_node:) corrector.replace( offense_range, add_safe_navigation_operator(offense_range: offense_range, send_node: send_node) ) corrector.wrap(send_node, '(', ')') if require_parentheses?(send_node) end
def brackets?(send_node)
def brackets?(send_node) send_node.method?(:[]) || send_node.method?(:[]=) end
def on_send(node)
def on_send(node) return unless require_safe_navigation?(node) bad_method?(node) do |safe_nav, method| return if nil_methods.include?(method) || PLUS_MINUS_METHODS.include?(node.method_name) begin_range = node.loc.dot || safe_nav.source_range.end location = begin_range.join(node.source_range.end) add_offense(location) do |corrector| autocorrect(corrector, offense_range: location, send_node: node) end end end
def operator_inside_hash?(send_node)
def operator_inside_hash?(send_node) # If an operator call (without a dot) is inside a hash, it needs # to be parenthesized when converted to safe navigation. send_node.parent&.pair_type? && !send_node.loc.dot end
def require_parentheses?(send_node)
def require_parentheses?(send_node) return true if operator_inside_hash?(send_node) return false unless send_node.comparison_method? return false unless (node = send_node.parent) (node.respond_to?(:logical_operator?) && node.logical_operator?) || (node.respond_to?(:comparison_method?) && node.comparison_method?) end
def require_safe_navigation?(node)
def require_safe_navigation?(node) parent = node.parent return true unless parent&.and_type? parent.rhs != node || parent.lhs.receiver != parent.rhs.receiver end