rubocop 1.84.2 → 1.85.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/config/default.yml +83 -4
- data/config/obsoletion.yml +5 -0
- data/lib/rubocop/cli/command/mcp.rb +19 -0
- data/lib/rubocop/cli.rb +6 -3
- data/lib/rubocop/config_obsoletion/extracted_cop.rb +4 -2
- data/lib/rubocop/cop/correctors/condition_corrector.rb +1 -1
- data/lib/rubocop/cop/correctors/percent_literal_corrector.rb +2 -2
- data/lib/rubocop/cop/gemspec/require_mfa.rb +1 -1
- data/lib/rubocop/cop/internal_affairs/itblock_handler.rb +69 -0
- data/lib/rubocop/cop/internal_affairs.rb +1 -0
- data/lib/rubocop/cop/layout/argument_alignment.rb +1 -1
- data/lib/rubocop/cop/layout/array_alignment.rb +1 -1
- data/lib/rubocop/cop/layout/empty_lines_around_block_body.rb +12 -2
- data/lib/rubocop/cop/layout/empty_lines_around_class_body.rb +16 -2
- data/lib/rubocop/cop/layout/empty_lines_around_module_body.rb +16 -2
- data/lib/rubocop/cop/layout/first_hash_element_indentation.rb +7 -1
- data/lib/rubocop/cop/layout/hash_alignment.rb +1 -1
- data/lib/rubocop/cop/layout/indentation_width.rb +1 -1
- data/lib/rubocop/cop/layout/multiline_assignment_layout.rb +9 -2
- data/lib/rubocop/cop/layout/parameter_alignment.rb +1 -1
- data/lib/rubocop/cop/layout/redundant_line_break.rb +1 -1
- data/lib/rubocop/cop/layout/space_around_block_parameters.rb +1 -1
- data/lib/rubocop/cop/layout/space_around_keyword.rb +1 -1
- data/lib/rubocop/cop/lint/constant_resolution.rb +1 -1
- data/lib/rubocop/cop/lint/data_define_override.rb +63 -0
- data/lib/rubocop/cop/lint/empty_block.rb +1 -1
- data/lib/rubocop/cop/lint/interpolation_check.rb +7 -2
- data/lib/rubocop/cop/lint/next_without_accumulator.rb +2 -0
- data/lib/rubocop/cop/lint/non_deterministic_require_order.rb +3 -1
- data/lib/rubocop/cop/lint/redundant_cop_enable_directive.rb +0 -9
- data/lib/rubocop/cop/lint/redundant_safe_navigation.rb +7 -6
- data/lib/rubocop/cop/lint/safe_navigation_consistency.rb +7 -1
- data/lib/rubocop/cop/lint/unmodified_reduce_accumulator.rb +1 -0
- data/lib/rubocop/cop/lint/unreachable_pattern_branch.rb +113 -0
- data/lib/rubocop/cop/lint/useless_assignment.rb +1 -1
- data/lib/rubocop/cop/lint/void.rb +32 -12
- data/lib/rubocop/cop/metrics/block_nesting.rb +23 -0
- data/lib/rubocop/cop/migration/department_name.rb +12 -1
- data/lib/rubocop/cop/mixin/check_line_breakable.rb +1 -1
- data/lib/rubocop/cop/mixin/check_single_line_suitability.rb +1 -1
- data/lib/rubocop/cop/mixin/hash_transform_method/autocorrection.rb +63 -0
- data/lib/rubocop/cop/mixin/hash_transform_method.rb +10 -60
- data/lib/rubocop/cop/naming/block_parameter_name.rb +1 -1
- data/lib/rubocop/cop/security/eval.rb +15 -2
- data/lib/rubocop/cop/style/accessor_grouping.rb +4 -2
- data/lib/rubocop/cop/style/alias.rb +4 -1
- data/lib/rubocop/cop/style/array_join.rb +4 -2
- data/lib/rubocop/cop/style/ascii_comments.rb +5 -2
- data/lib/rubocop/cop/style/attr.rb +5 -2
- data/lib/rubocop/cop/style/bare_percent_literals.rb +3 -1
- data/lib/rubocop/cop/style/begin_block.rb +3 -1
- data/lib/rubocop/cop/style/block_delimiters.rb +2 -2
- data/lib/rubocop/cop/style/case_equality.rb +4 -0
- data/lib/rubocop/cop/style/class_and_module_children.rb +10 -2
- data/lib/rubocop/cop/style/colon_method_call.rb +3 -1
- data/lib/rubocop/cop/style/copyright.rb +1 -1
- data/lib/rubocop/cop/style/each_for_simple_loop.rb +1 -1
- data/lib/rubocop/cop/style/each_with_object.rb +2 -0
- data/lib/rubocop/cop/style/empty_block_parameter.rb +1 -1
- data/lib/rubocop/cop/style/empty_class_definition.rb +21 -20
- data/lib/rubocop/cop/style/empty_lambda_parameter.rb +1 -1
- data/lib/rubocop/cop/style/encoding.rb +7 -1
- data/lib/rubocop/cop/style/end_block.rb +3 -1
- data/lib/rubocop/cop/style/endless_method.rb +8 -3
- data/lib/rubocop/cop/style/file_open.rb +63 -0
- data/lib/rubocop/cop/style/for.rb +3 -0
- data/lib/rubocop/cop/style/format_string_token.rb +29 -2
- data/lib/rubocop/cop/style/global_vars.rb +4 -1
- data/lib/rubocop/cop/style/hash_as_last_array_item.rb +21 -5
- data/lib/rubocop/cop/style/hash_transform_keys.rb +17 -7
- data/lib/rubocop/cop/style/hash_transform_values.rb +17 -7
- data/lib/rubocop/cop/style/if_unless_modifier.rb +3 -3
- data/lib/rubocop/cop/style/inline_comment.rb +4 -1
- data/lib/rubocop/cop/style/map_join.rb +123 -0
- data/lib/rubocop/cop/style/multiline_if_then.rb +3 -1
- data/lib/rubocop/cop/style/nil_comparison.rb +2 -3
- data/lib/rubocop/cop/style/nil_lambda.rb +1 -1
- data/lib/rubocop/cop/style/not.rb +2 -0
- data/lib/rubocop/cop/style/numeric_literals.rb +2 -1
- data/lib/rubocop/cop/style/one_class_per_file.rb +95 -0
- data/lib/rubocop/cop/style/one_line_conditional.rb +4 -3
- data/lib/rubocop/cop/style/parallel_assignment.rb +4 -0
- data/lib/rubocop/cop/style/partition_instead_of_double_select.rb +270 -0
- data/lib/rubocop/cop/style/percent_literal_delimiters.rb +2 -0
- data/lib/rubocop/cop/style/predicate_with_kind.rb +84 -0
- data/lib/rubocop/cop/style/proc.rb +3 -2
- data/lib/rubocop/cop/style/reduce_to_hash.rb +169 -0
- data/lib/rubocop/cop/style/redundant_begin.rb +3 -3
- data/lib/rubocop/cop/style/redundant_fetch_block.rb +1 -1
- data/lib/rubocop/cop/style/redundant_interpolation_unfreeze.rb +26 -10
- data/lib/rubocop/cop/style/redundant_min_max_by.rb +93 -0
- data/lib/rubocop/cop/style/redundant_parentheses.rb +6 -3
- data/lib/rubocop/cop/style/redundant_return.rb +3 -1
- data/lib/rubocop/cop/style/redundant_struct_keyword_init.rb +104 -0
- data/lib/rubocop/cop/style/select_by_kind.rb +158 -0
- data/lib/rubocop/cop/style/select_by_range.rb +197 -0
- data/lib/rubocop/cop/style/select_by_regexp.rb +51 -21
- data/lib/rubocop/cop/style/semicolon.rb +2 -0
- data/lib/rubocop/cop/style/single_line_block_params.rb +1 -1
- data/lib/rubocop/cop/style/single_line_do_end_block.rb +1 -1
- data/lib/rubocop/cop/style/single_line_methods.rb +3 -1
- data/lib/rubocop/cop/style/special_global_vars.rb +6 -1
- data/lib/rubocop/cop/style/tally_method.rb +181 -0
- data/lib/rubocop/cop/style/trailing_comma_in_block_args.rb +1 -1
- data/lib/rubocop/cop/variable_force/branch.rb +2 -2
- data/lib/rubocop/directive_comment.rb +2 -1
- data/lib/rubocop/formatter/formatter_set.rb +1 -1
- data/lib/rubocop/lsp/diagnostic.rb +1 -0
- data/lib/rubocop/mcp/server.rb +174 -0
- data/lib/rubocop/options.rb +10 -1
- data/lib/rubocop/server/cache.rb +5 -7
- data/lib/rubocop/target_ruby.rb +18 -12
- data/lib/rubocop/version.rb +1 -1
- data/lib/rubocop.rb +14 -0
- metadata +34 -3
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RuboCop
|
|
4
|
+
module Cop
|
|
5
|
+
module Style
|
|
6
|
+
# Checks for consecutive calls to `select`/`filter`/`find_all` and `reject`
|
|
7
|
+
# on the same receiver with the same block body, where `partition` could be
|
|
8
|
+
# used instead. Also detects two `select` or two `reject` calls where one
|
|
9
|
+
# block negates the other with `!`. Using `partition` reduces two collection
|
|
10
|
+
# traversals to one.
|
|
11
|
+
#
|
|
12
|
+
# @safety
|
|
13
|
+
# This cop is unsafe because:
|
|
14
|
+
#
|
|
15
|
+
# * `Hash#select` and `Hash#reject` return hashes, but `Hash#partition`
|
|
16
|
+
# returns nested arrays.
|
|
17
|
+
# * When the receiver has side effects, calling it once (with `partition`)
|
|
18
|
+
# versus twice (with `select` + `reject`) may produce different results.
|
|
19
|
+
# * Custom classes may override `select`/`reject` without providing a
|
|
20
|
+
# compatible `partition` method.
|
|
21
|
+
#
|
|
22
|
+
# @example
|
|
23
|
+
# # bad
|
|
24
|
+
# positives = array.select { |x| x > 0 }
|
|
25
|
+
# negatives = array.reject { |x| x > 0 }
|
|
26
|
+
#
|
|
27
|
+
# # bad
|
|
28
|
+
# positives = array.filter { |x| x > 0 }
|
|
29
|
+
# negatives = array.reject { |x| x > 0 }
|
|
30
|
+
#
|
|
31
|
+
# # bad
|
|
32
|
+
# negatives = array.reject { |x| x > 0 }
|
|
33
|
+
# positives = array.select { |x| x > 0 }
|
|
34
|
+
#
|
|
35
|
+
# # bad
|
|
36
|
+
# positives = array.select(&:positive?)
|
|
37
|
+
# negatives = array.reject(&:positive?)
|
|
38
|
+
#
|
|
39
|
+
# # bad
|
|
40
|
+
# positives = array.select(&:positive?)
|
|
41
|
+
# negatives = array.reject { |x| x.positive? }
|
|
42
|
+
#
|
|
43
|
+
# # bad
|
|
44
|
+
# positives = array.select { |x| x.positive? }
|
|
45
|
+
# non_positives = array.select { |x| !x.positive? }
|
|
46
|
+
#
|
|
47
|
+
# # good
|
|
48
|
+
# positives, negatives = array.partition { |x| x > 0 }
|
|
49
|
+
#
|
|
50
|
+
# # good
|
|
51
|
+
# positives, non_positives = array.partition { |x| x.positive? }
|
|
52
|
+
#
|
|
53
|
+
# # good
|
|
54
|
+
# positives, negatives = array.partition(&:positive?)
|
|
55
|
+
#
|
|
56
|
+
class PartitionInsteadOfDoubleSelect < Base
|
|
57
|
+
include RangeHelp
|
|
58
|
+
extend AutoCorrector
|
|
59
|
+
|
|
60
|
+
MSG = 'Use `partition` instead of consecutive `%<first>s` and `%<second>s` calls.'
|
|
61
|
+
|
|
62
|
+
SELECT_METHODS = %i[select filter find_all].freeze
|
|
63
|
+
CANDIDATE_METHODS = (SELECT_METHODS + %i[reject]).to_set.freeze
|
|
64
|
+
RESTRICT_ON_SEND = (SELECT_METHODS + %i[reject]).freeze
|
|
65
|
+
|
|
66
|
+
# @!method symbol_proc_method?(node)
|
|
67
|
+
def_node_matcher :symbol_proc_method?, <<~PATTERN
|
|
68
|
+
(block _ (args (arg _name)) (send (lvar _name) $_method_name))
|
|
69
|
+
PATTERN
|
|
70
|
+
|
|
71
|
+
def on_block(node)
|
|
72
|
+
return unless CANDIDATE_METHODS.include?(node.method_name)
|
|
73
|
+
|
|
74
|
+
find_and_register_offense(node)
|
|
75
|
+
end
|
|
76
|
+
alias on_numblock on_block
|
|
77
|
+
alias on_itblock on_block
|
|
78
|
+
|
|
79
|
+
def on_send(node)
|
|
80
|
+
return unless node.last_argument&.block_pass_type?
|
|
81
|
+
|
|
82
|
+
find_and_register_offense(node)
|
|
83
|
+
end
|
|
84
|
+
alias on_csend on_send
|
|
85
|
+
|
|
86
|
+
private
|
|
87
|
+
|
|
88
|
+
def find_and_register_offense(node)
|
|
89
|
+
container = node_container(node)
|
|
90
|
+
return unless container
|
|
91
|
+
|
|
92
|
+
sibling_container = container.left_sibling
|
|
93
|
+
sibling = find_matching_candidate(node, sibling_container)
|
|
94
|
+
return unless sibling
|
|
95
|
+
|
|
96
|
+
register_offense(node, sibling, container, sibling_container)
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def node_container(node)
|
|
100
|
+
parent = node.parent
|
|
101
|
+
if parent&.begin_type?
|
|
102
|
+
node
|
|
103
|
+
elsif parent&.assignment? && parent.parent&.begin_type?
|
|
104
|
+
parent
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def find_matching_candidate(node, sibling_container)
|
|
109
|
+
return unless sibling_container
|
|
110
|
+
|
|
111
|
+
sibling = extract_candidate(sibling_container)
|
|
112
|
+
return unless sibling
|
|
113
|
+
return unless node.receiver == sibling.receiver
|
|
114
|
+
return unless matching_pair?(node, sibling)
|
|
115
|
+
|
|
116
|
+
sibling
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def matching_pair?(node, sibling)
|
|
120
|
+
(complementary_pair?(node, sibling) && equivalent_predicate?(node, sibling)) ||
|
|
121
|
+
(node.method?(sibling.method_name) && negated_predicate?(node, sibling))
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def extract_candidate(container)
|
|
125
|
+
extract_block(container) || extract_block_pass_send(container)
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def extract_block(container)
|
|
129
|
+
if container.any_block_type?
|
|
130
|
+
container
|
|
131
|
+
elsif container.assignment?
|
|
132
|
+
rhs = container.children.last
|
|
133
|
+
rhs if rhs&.any_block_type?
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def extract_block_pass_send(container)
|
|
138
|
+
node = container.assignment? ? container.children.last : container
|
|
139
|
+
return unless node&.type?(:call)
|
|
140
|
+
return unless node.last_argument&.block_pass_type?
|
|
141
|
+
|
|
142
|
+
node
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
def complementary_pair?(node1, node2)
|
|
146
|
+
m1 = node1.method_name
|
|
147
|
+
m2 = node2.method_name
|
|
148
|
+
(SELECT_METHODS.include?(m1) && m2 == :reject) ||
|
|
149
|
+
(m1 == :reject && SELECT_METHODS.include?(m2))
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
def equivalent_predicate?(node1, node2)
|
|
153
|
+
if node1.any_block_type? && node2.any_block_type?
|
|
154
|
+
same_block_contents?(node1, node2)
|
|
155
|
+
elsif node1.any_block_type?
|
|
156
|
+
block_matches_block_pass?(node1, node2)
|
|
157
|
+
elsif node2.any_block_type?
|
|
158
|
+
block_matches_block_pass?(node2, node1)
|
|
159
|
+
else
|
|
160
|
+
node1.last_argument == node2.last_argument
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
def same_block_contents?(block1, block2)
|
|
165
|
+
return false unless block1.type == block2.type
|
|
166
|
+
|
|
167
|
+
if block1.block_type?
|
|
168
|
+
block1.arguments == block2.arguments &&
|
|
169
|
+
block1.body == block2.body
|
|
170
|
+
else
|
|
171
|
+
block1.body == block2.body
|
|
172
|
+
end
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
def block_matches_block_pass?(block_node, send_node)
|
|
176
|
+
method_name = symbol_proc_method?(block_node)
|
|
177
|
+
return false unless method_name
|
|
178
|
+
|
|
179
|
+
sym_node = send_node.last_argument.children.first
|
|
180
|
+
sym_node.sym_type? && sym_node.children.first == method_name
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
def negated_predicate?(node1, node2)
|
|
184
|
+
return false unless node1.any_block_type? && node2.any_block_type?
|
|
185
|
+
return false unless node1.type == node2.type
|
|
186
|
+
return false if node1.block_type? && node1.arguments != node2.arguments
|
|
187
|
+
|
|
188
|
+
negated_body?(node1.body, node2.body) || negated_body?(node2.body, node1.body)
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
def negated_body?(body1, body2)
|
|
192
|
+
body1&.send_type? && body1.method?(:!) && body1.receiver == body2
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
def register_offense(node, sibling, container, sibling_container)
|
|
196
|
+
message = format(MSG, first: sibling.method_name, second: node.method_name)
|
|
197
|
+
|
|
198
|
+
add_offense(container, message: message) do |corrector|
|
|
199
|
+
next unless both_lvasgn?(container, sibling_container)
|
|
200
|
+
|
|
201
|
+
autocorrect(corrector, node, sibling, container, sibling_container)
|
|
202
|
+
end
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
def both_lvasgn?(container, sibling_container)
|
|
206
|
+
container.lvasgn_type? && sibling_container.lvasgn_type?
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
def autocorrect(corrector, node, sibling, container, sibling_container)
|
|
210
|
+
if complementary_pair?(node, sibling)
|
|
211
|
+
select_var, reject_var =
|
|
212
|
+
complementary_variable_order(sibling, container, sibling_container)
|
|
213
|
+
partition_node = select_node_for(sibling, container)
|
|
214
|
+
else
|
|
215
|
+
select_var, reject_var, partition_node =
|
|
216
|
+
negation_partition_args(node, sibling, container, sibling_container)
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
partition_call = build_partition_call(partition_node)
|
|
220
|
+
replacement = "#{select_var}, #{reject_var} = #{partition_call}"
|
|
221
|
+
|
|
222
|
+
corrector.replace(sibling_container, replacement)
|
|
223
|
+
range = range_by_whole_lines(container.source_range, include_final_newline: true)
|
|
224
|
+
corrector.remove(range)
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
def complementary_variable_order(sibling, container, sibling_container)
|
|
228
|
+
if SELECT_METHODS.include?(sibling.method_name)
|
|
229
|
+
[sibling_container.children.first, container.children.first]
|
|
230
|
+
else
|
|
231
|
+
[container.children.first, sibling_container.children.first]
|
|
232
|
+
end
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
def negation_partition_args(node, sibling, container, sibling_container)
|
|
236
|
+
node_is_negated = negated_body?(node.body, sibling.body)
|
|
237
|
+
is_select = SELECT_METHODS.include?(node.method_name)
|
|
238
|
+
# For select: non-negated is truthy (first). For reject: negated is truthy (first).
|
|
239
|
+
node_is_truthy = is_select != node_is_negated
|
|
240
|
+
partition_node = node_is_negated ? sibling : node
|
|
241
|
+
|
|
242
|
+
if node_is_truthy
|
|
243
|
+
[container.children.first, sibling_container.children.first, partition_node]
|
|
244
|
+
else
|
|
245
|
+
[sibling_container.children.first, container.children.first, partition_node]
|
|
246
|
+
end
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
def select_node_for(sibling, container)
|
|
250
|
+
if SELECT_METHODS.include?(sibling.method_name)
|
|
251
|
+
sibling
|
|
252
|
+
else
|
|
253
|
+
container.children.last
|
|
254
|
+
end
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
def build_partition_call(node)
|
|
258
|
+
source = node.source
|
|
259
|
+
send_node = node.any_block_type? ? node.send_node : node
|
|
260
|
+
selector = send_node.loc.selector
|
|
261
|
+
offset = node.source_range.begin_pos
|
|
262
|
+
method_start = selector.begin_pos - offset
|
|
263
|
+
method_end = selector.end_pos - offset
|
|
264
|
+
|
|
265
|
+
"#{source[0...method_start]}partition#{source[method_end..]}"
|
|
266
|
+
end
|
|
267
|
+
end
|
|
268
|
+
end
|
|
269
|
+
end
|
|
270
|
+
end
|
|
@@ -4,6 +4,8 @@ module RuboCop
|
|
|
4
4
|
module Cop
|
|
5
5
|
module Style
|
|
6
6
|
# Enforces the consistent usage of `%`-literal delimiters.
|
|
7
|
+
# Using consistent delimiters across the codebase reduces
|
|
8
|
+
# cognitive load when reading `%`-literals.
|
|
7
9
|
#
|
|
8
10
|
# Specify the 'default' key to set all preferred delimiters at once. You
|
|
9
11
|
# can continue to specify individual preferred delimiters to override the
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RuboCop
|
|
4
|
+
module Cop
|
|
5
|
+
module Style
|
|
6
|
+
# Looks for uses of `any?`, `all?`, `none?`, or `one?` with a block
|
|
7
|
+
# containing only an `is_a?`, `kind_of?`, or `instance_of?` check, and
|
|
8
|
+
# suggests using the predicate method with the class argument directly.
|
|
9
|
+
#
|
|
10
|
+
# @safety
|
|
11
|
+
# This cop is unsafe because `instance_of?` checks for an exact class
|
|
12
|
+
# match, while the pattern argument uses `===` which also matches
|
|
13
|
+
# subclasses. For `is_a?` and `kind_of?`, the behavior is equivalent.
|
|
14
|
+
#
|
|
15
|
+
# @example
|
|
16
|
+
# # bad
|
|
17
|
+
# array.any? { |x| x.is_a?(Integer) }
|
|
18
|
+
# array.all? { |x| x.kind_of?(String) }
|
|
19
|
+
# array.none? { |x| x.is_a?(Float) }
|
|
20
|
+
# array.one? { |x| x.instance_of?(Symbol) }
|
|
21
|
+
#
|
|
22
|
+
# # good
|
|
23
|
+
# array.any?(Integer)
|
|
24
|
+
# array.all?(String)
|
|
25
|
+
# array.none?(Float)
|
|
26
|
+
# array.one?(Symbol)
|
|
27
|
+
class PredicateWithKind < Base
|
|
28
|
+
extend AutoCorrector
|
|
29
|
+
include RangeHelp
|
|
30
|
+
|
|
31
|
+
MSG = 'Prefer `%<replacement>s` to `%<original>s` with a kind check.'
|
|
32
|
+
RESTRICT_ON_SEND = %i[any? all? none? one?].freeze
|
|
33
|
+
KIND_METHODS = %i[is_a? kind_of? instance_of?].to_set.freeze
|
|
34
|
+
|
|
35
|
+
# @!method kind_check?(node)
|
|
36
|
+
def_node_matcher :kind_check?, <<~PATTERN
|
|
37
|
+
{
|
|
38
|
+
(block call (args (arg $_)) $(send (lvar _) %KIND_METHODS _))
|
|
39
|
+
(numblock call $1 $(send (lvar _) %KIND_METHODS _))
|
|
40
|
+
(itblock call $_ $(send (lvar _) %KIND_METHODS _))
|
|
41
|
+
}
|
|
42
|
+
PATTERN
|
|
43
|
+
|
|
44
|
+
# @!method kind_call?(node, name)
|
|
45
|
+
def_node_matcher :kind_call?, <<~PATTERN
|
|
46
|
+
(send (lvar %1) %KIND_METHODS _)
|
|
47
|
+
PATTERN
|
|
48
|
+
|
|
49
|
+
def on_send(node)
|
|
50
|
+
return unless (block_node = node.block_node)
|
|
51
|
+
return if block_node.body&.begin_type?
|
|
52
|
+
return unless (kind_check_node = extract_send_node(block_node))
|
|
53
|
+
|
|
54
|
+
klass = kind_check_node.first_argument
|
|
55
|
+
replacement = "#{node.method_name}(#{klass.source})"
|
|
56
|
+
|
|
57
|
+
register_offense(node, block_node, klass, replacement)
|
|
58
|
+
end
|
|
59
|
+
alias on_csend on_send
|
|
60
|
+
|
|
61
|
+
private
|
|
62
|
+
|
|
63
|
+
def extract_send_node(block_node)
|
|
64
|
+
return unless (block_arg_name, kind_check_node = kind_check?(block_node))
|
|
65
|
+
|
|
66
|
+
block_arg_name = :"_#{block_arg_name}" if block_node.numblock_type?
|
|
67
|
+
block_arg_name = :it if block_node.type?(:itblock)
|
|
68
|
+
|
|
69
|
+
kind_check_node if kind_call?(kind_check_node, block_arg_name)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def register_offense(node, block_node, klass, replacement)
|
|
73
|
+
original = "#{node.method_name} { ... }"
|
|
74
|
+
message = format(MSG, replacement: replacement, original: original)
|
|
75
|
+
|
|
76
|
+
add_offense(block_node, message: message) do |corrector|
|
|
77
|
+
range = range_between(node.loc.selector.begin_pos, block_node.loc.end.end_pos)
|
|
78
|
+
corrector.replace(range, "#{node.method_name}(#{klass.source})")
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
@@ -3,8 +3,9 @@
|
|
|
3
3
|
module RuboCop
|
|
4
4
|
module Cop
|
|
5
5
|
module Style
|
|
6
|
-
# Checks for uses of Proc.new where Kernel#proc
|
|
7
|
-
# would be more appropriate.
|
|
6
|
+
# Checks for uses of `Proc.new` where `Kernel#proc`
|
|
7
|
+
# would be more appropriate. `proc` is the shorter and
|
|
8
|
+
# more idiomatic way to create procs in Ruby.
|
|
8
9
|
#
|
|
9
10
|
# @example
|
|
10
11
|
# # bad
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RuboCop
|
|
4
|
+
module Cop
|
|
5
|
+
module Style
|
|
6
|
+
# Checks for `each_with_object`, `inject`, and `reduce` calls that build
|
|
7
|
+
# a hash from an enumerable, where `to_h` with a block could be used instead.
|
|
8
|
+
#
|
|
9
|
+
# This cop complements `Style/HashTransformKeys` and `Style/HashTransformValues`,
|
|
10
|
+
# which handle hash-to-hash transformations with destructured key-value pairs.
|
|
11
|
+
# This cop targets the case where a hash is built from individual elements
|
|
12
|
+
# (non-destructured block parameter).
|
|
13
|
+
#
|
|
14
|
+
# @safety
|
|
15
|
+
# This cop is unsafe because it cannot guarantee that the receiver
|
|
16
|
+
# is an `Enumerable` by static analysis, so the correction may
|
|
17
|
+
# not be actually equivalent. Additionally, `each_with_object` returns
|
|
18
|
+
# the hash object while `to_h` returns a new hash, which could matter
|
|
19
|
+
# if the hash object identity is important.
|
|
20
|
+
#
|
|
21
|
+
# @example
|
|
22
|
+
# # bad
|
|
23
|
+
# array.each_with_object({}) { |elem, hash| hash[elem.id] = elem.name }
|
|
24
|
+
#
|
|
25
|
+
# # bad
|
|
26
|
+
# array.inject({}) { |hash, elem| hash[elem.id] = elem.name; hash }
|
|
27
|
+
#
|
|
28
|
+
# # bad
|
|
29
|
+
# array.reduce({}) { |hash, elem| hash[elem.id] = elem.name; hash }
|
|
30
|
+
#
|
|
31
|
+
# # bad
|
|
32
|
+
# array.each_with_object({}) { |elem, hash| hash[elem] = elem.to_s }
|
|
33
|
+
#
|
|
34
|
+
# # good
|
|
35
|
+
# array.to_h { |elem| [elem.id, elem.name] }
|
|
36
|
+
#
|
|
37
|
+
# # good
|
|
38
|
+
# array.to_h { |elem| [elem, elem.to_s] }
|
|
39
|
+
#
|
|
40
|
+
class ReduceToHash < Base
|
|
41
|
+
extend AutoCorrector
|
|
42
|
+
extend TargetRubyVersion
|
|
43
|
+
include RangeHelp
|
|
44
|
+
|
|
45
|
+
minimum_target_ruby_version 2.6
|
|
46
|
+
|
|
47
|
+
MSG = 'Use `to_h { ... }` instead of `%<method>s`.'
|
|
48
|
+
RESTRICT_ON_SEND = %i[each_with_object inject reduce].freeze
|
|
49
|
+
|
|
50
|
+
# each_with_object({}) { |elem, hash| hash[key] = value }
|
|
51
|
+
# @!method each_with_object_to_hash?(node)
|
|
52
|
+
def_node_matcher :each_with_object_to_hash?, <<~PATTERN
|
|
53
|
+
{
|
|
54
|
+
(block
|
|
55
|
+
(call _ :each_with_object (hash))
|
|
56
|
+
(args (arg _elem) (arg _hash))
|
|
57
|
+
(send (lvar _hash) :[]= $_key $_value))
|
|
58
|
+
(numblock
|
|
59
|
+
(call _ :each_with_object (hash))
|
|
60
|
+
2
|
|
61
|
+
(send (lvar :_2) :[]= $_key $_value))
|
|
62
|
+
}
|
|
63
|
+
PATTERN
|
|
64
|
+
|
|
65
|
+
# inject/reduce({}) { |hash, elem| hash[key] = value; hash }
|
|
66
|
+
# @!method inject_to_hash?(node)
|
|
67
|
+
def_node_matcher :inject_to_hash?, <<~PATTERN
|
|
68
|
+
{
|
|
69
|
+
(block
|
|
70
|
+
(call _ {:inject :reduce} (hash))
|
|
71
|
+
(args (arg _hash) (arg _elem))
|
|
72
|
+
(begin
|
|
73
|
+
(send (lvar _hash) :[]= $_key $_value)
|
|
74
|
+
(lvar _hash)))
|
|
75
|
+
(numblock
|
|
76
|
+
(call _ {:inject :reduce} (hash))
|
|
77
|
+
2
|
|
78
|
+
(begin
|
|
79
|
+
(send (lvar :_1) :[]= $_key $_value)
|
|
80
|
+
(lvar :_1)))
|
|
81
|
+
}
|
|
82
|
+
PATTERN
|
|
83
|
+
|
|
84
|
+
def on_send(node)
|
|
85
|
+
block_node = node.block_node
|
|
86
|
+
return unless block_node
|
|
87
|
+
|
|
88
|
+
check_offense(node, block_node)
|
|
89
|
+
end
|
|
90
|
+
alias on_csend on_send
|
|
91
|
+
|
|
92
|
+
private
|
|
93
|
+
|
|
94
|
+
def check_offense(node, block_node)
|
|
95
|
+
key, value = if node.method?(:each_with_object)
|
|
96
|
+
each_with_object_to_hash?(block_node)
|
|
97
|
+
else
|
|
98
|
+
inject_to_hash?(block_node)
|
|
99
|
+
end
|
|
100
|
+
return unless key
|
|
101
|
+
|
|
102
|
+
register_offense(node, block_node, key, value)
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def register_offense(send_node, block_node, key_expr, value_expr)
|
|
106
|
+
message = format(MSG, method: send_node.method_name)
|
|
107
|
+
|
|
108
|
+
add_offense(send_node.loc.selector, message: message) do |corrector|
|
|
109
|
+
corrector.replace(
|
|
110
|
+
replacement_range(send_node, block_node),
|
|
111
|
+
replacement(block_node, key_expr, value_expr)
|
|
112
|
+
)
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def replacement(block_node, key_expr, value_expr)
|
|
117
|
+
key_source = adjusted_source(key_expr, block_node)
|
|
118
|
+
value_source = adjusted_source(value_expr, block_node)
|
|
119
|
+
body = "[#{key_source}, #{value_source}]"
|
|
120
|
+
|
|
121
|
+
if block_node.numblock_type?
|
|
122
|
+
block_node.braces? ? "to_h { #{body} }" : do_end_replacement(block_node, body)
|
|
123
|
+
else
|
|
124
|
+
named_block_replacement(block_node, body)
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def named_block_replacement(block_node, body)
|
|
129
|
+
arg = element_arg_source(block_node)
|
|
130
|
+
if block_node.braces?
|
|
131
|
+
"to_h { |#{arg}| #{body} }"
|
|
132
|
+
else
|
|
133
|
+
do_end_replacement(block_node, body, arg)
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def do_end_replacement(block_node, body, arg = nil)
|
|
138
|
+
args = arg ? " |#{arg}|" : ''
|
|
139
|
+
"to_h do#{args}\n#{indent(block_node)} #{body}\n#{indent(block_node)}end"
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
def replacement_range(send_node, block_node)
|
|
143
|
+
range_between(send_node.loc.selector.begin_pos, block_node.source_range.end_pos)
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def element_arg_source(block_node)
|
|
147
|
+
if block_node.method?(:each_with_object)
|
|
148
|
+
block_node.first_argument.source
|
|
149
|
+
else
|
|
150
|
+
block_node.arguments[1].source
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
def adjusted_source(expr_node, block_node)
|
|
155
|
+
source = expr_node.source
|
|
156
|
+
return source unless block_node.numblock_type?
|
|
157
|
+
return source if block_node.method?(:each_with_object)
|
|
158
|
+
|
|
159
|
+
# For inject/reduce numblocks, _2 is the element (becomes _1)
|
|
160
|
+
source.gsub('_2', '_1')
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
def indent(node)
|
|
164
|
+
' ' * node.source_range.column
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
end
|
|
@@ -3,9 +3,9 @@
|
|
|
3
3
|
module RuboCop
|
|
4
4
|
module Cop
|
|
5
5
|
module Style
|
|
6
|
-
# Checks for redundant `begin` blocks.
|
|
7
|
-
#
|
|
8
|
-
#
|
|
6
|
+
# Checks for redundant `begin` blocks. A `begin` block is redundant
|
|
7
|
+
# when the `rescue`/`ensure` can be handled by the enclosing method
|
|
8
|
+
# or block definition directly, avoiding unnecessary indentation.
|
|
9
9
|
#
|
|
10
10
|
# @example
|
|
11
11
|
#
|
|
@@ -52,7 +52,7 @@ module RuboCop
|
|
|
52
52
|
${nil? basic_literal? const_type?})
|
|
53
53
|
PATTERN
|
|
54
54
|
|
|
55
|
-
def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler
|
|
55
|
+
def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler, InternalAffairs/ItblockHandler
|
|
56
56
|
redundant_fetch_block_candidate?(node) do |send, body|
|
|
57
57
|
return if should_not_check?(send, body)
|
|
58
58
|
|
|
@@ -15,6 +15,9 @@ module RuboCop
|
|
|
15
15
|
# # bad
|
|
16
16
|
# "#{foo} bar".dup
|
|
17
17
|
#
|
|
18
|
+
# # bad
|
|
19
|
+
# String.new("#{foo} bar")
|
|
20
|
+
#
|
|
18
21
|
# # good
|
|
19
22
|
# "#{foo} bar"
|
|
20
23
|
#
|
|
@@ -25,19 +28,32 @@ module RuboCop
|
|
|
25
28
|
|
|
26
29
|
MSG = "Don't unfreeze interpolated strings as they are already unfrozen."
|
|
27
30
|
|
|
28
|
-
RESTRICT_ON_SEND = %i[+@ dup].freeze
|
|
29
|
-
|
|
30
31
|
minimum_target_ruby_version 3.0
|
|
31
32
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
33
|
+
# @!method redundant_unfreeze?(node)
|
|
34
|
+
def_node_matcher :redundant_unfreeze?, <<~PATTERN
|
|
35
|
+
{
|
|
36
|
+
(send dstr_type? {:+@ :dup})
|
|
37
|
+
(send (const nil? :String) :new dstr_type?)
|
|
38
|
+
}
|
|
39
|
+
PATTERN
|
|
40
|
+
|
|
41
|
+
def on_dstr(node)
|
|
42
|
+
return if uninterpolated_string?(node) || uninterpolated_heredoc?(node)
|
|
43
|
+
return unless redundant_unfreeze?(node.parent)
|
|
44
|
+
|
|
45
|
+
add_offense(offense_range(node.parent)) do |corrector|
|
|
46
|
+
corrector.replace(node.parent, node.source)
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
private
|
|
37
51
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
52
|
+
def offense_range(node)
|
|
53
|
+
if node.method?(:new)
|
|
54
|
+
node.source_range.begin.join(node.loc.selector)
|
|
55
|
+
else
|
|
56
|
+
node.loc.selector
|
|
41
57
|
end
|
|
42
58
|
end
|
|
43
59
|
end
|