/
map_compact_with_conditional_block.rb
134 lines (119 loc) · 3.51 KB
/
map_compact_with_conditional_block.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
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
# frozen_string_literal: true
module RuboCop
module Cop
module Style
# Prefer `select` or `reject` over `map { ... }.compact`.
#
# @example
#
# # bad
# array.map { |e| some_condition? ? e : next }.compact
#
# # bad
# array.map do |e|
# if some_condition?
# e
# else
# next
# end
# end.compact
#
# # bad
# array.map do |e|
# next if some_condition?
#
# e
# end.compact
#
# # bad
# array.map do |e|
# e if some_condition?
# end.compact
#
# # good
# array.select { |e| some_condition? }
#
# # good
# array.reject { |e| some_condition? }
#
class MapCompactWithConditionalBlock < Base
extend AutoCorrector
MSG = 'Replace `map { ... }.compact` with `%<method>s`.'
# @!method map_and_compact?(node)
def_node_matcher :map_and_compact?, <<~RUBY
(call
(block
(call _ :map)
(args
$(arg _))
{
(if $_ $(lvar _) {next nil?})
(if $_ {next nil?} $(lvar _))
(if $_ (next $(lvar _)) {next nil nil?})
(if $_ {next nil nil?} (next $(lvar _)))
(begin
{
(if $_ next nil?)
(if $_ nil? next)
}
$(lvar _))
(begin
{
(if $_ (next $(lvar _)) nil?)
(if $_ nil? (next $(lvar _)))
}
(nil))
}) :compact)
RUBY
def on_send(node)
map_and_compact?(node) do |block_argument_node, condition_node, return_value_node|
return unless returns_block_argument?(block_argument_node, return_value_node)
return if condition_node.parent.elsif?
method = truthy_branch?(return_value_node) ? 'select' : 'reject'
range = range(node)
add_offense(range, message: format(MSG, method: method)) do |corrector|
corrector.replace(
range,
"#{method} { |#{block_argument_node.source}| #{condition_node.source} }"
)
end
end
end
alias on_csend on_send
private
def returns_block_argument?(block_argument_node, return_value_node)
block_argument_node.name == return_value_node.children.first
end
def truthy_branch?(node)
if node.parent.begin_type?
truthy_branch_for_guard?(node)
elsif node.parent.next_type?
truthy_branch_for_if?(node.parent)
else
truthy_branch_for_if?(node)
end
end
def truthy_branch_for_if?(node)
if_node = node.parent
if if_node.if? || if_node.ternary?
if_node.if_branch == node
elsif if_node.unless?
if_node.else_branch == node
end
end
def truthy_branch_for_guard?(node)
if_node = node.left_sibling
if if_node.if?
if_node.if_branch.arguments.any?
else
if_node.if_branch.arguments.none?
end
end
def range(node)
map_node = node.receiver.send_node
map_node.loc.selector.join(node.source_range.end)
end
end
end
end
end