forked from rubocop/rubocop
/
safe_navigation_chain.rb
62 lines (55 loc) · 1.65 KB
/
safe_navigation_chain.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
# frozen_string_literal: true
module RuboCop
module Cop
module Lint
# The safe navigation operator returns nil if the receiver is
# nil. If you chain an ordinary method call after a safe
# navigation operator, it raises NoMethodError. We should use a
# safe navigation operator after a safe navigation operator.
# This cop checks for the problem outlined above.
#
# @example
#
# # bad
#
# x&.foo.bar
# x&.foo + bar
# x&.foo[bar]
#
# @example
#
# # good
#
# x&.foo&.bar
# x&.foo || bar
class SafeNavigationChain < Cop
include NilMethods
MSG = 'Do not chain ordinary method call' \
' after safe navigation operator.'
def_node_matcher :bad_method?, <<~PATTERN
{
(send $(csend ...) $_ ...)
(send $({block numblock} (csend ...) ...) $_ ...)
}
PATTERN
def on_send(node)
bad_method?(node) do |safe_nav, method|
return if nil_methods.include?(method)
method_chain = method_chain(node)
location =
Parser::Source::Range.new(node.source_range.source_buffer,
safe_nav.source_range.end_pos,
method_chain.source_range.end_pos)
add_offense(node, location: location)
end
end
private
def method_chain(node)
chain = node
chain = chain.parent if chain.send_type? && chain.parent&.call_type?
chain
end
end
end
end
end