rubocop 1.92.0 → 1.92.2
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 +1 -0
- data/lib/rubocop/config.rb +0 -4
- data/lib/rubocop/config_obsoletion.rb +9 -0
- data/lib/rubocop/cop/badge.rb +1 -1
- data/lib/rubocop/cop/correctors/each_to_for_corrector.rb +16 -1
- data/lib/rubocop/cop/correctors/empty_line_corrector.rb +1 -1
- data/lib/rubocop/cop/correctors/lambda_literal_to_method_corrector.rb +2 -2
- data/lib/rubocop/cop/correctors/multiline_literal_brace_corrector.rb +34 -0
- data/lib/rubocop/cop/layout/argument_alignment.rb +5 -1
- data/lib/rubocop/cop/layout/class_structure.rb +21 -7
- data/lib/rubocop/cop/layout/dot_position.rb +25 -15
- data/lib/rubocop/cop/layout/empty_line_after_multiline_condition.rb +11 -7
- data/lib/rubocop/cop/layout/empty_lines_after_module_inclusion.rb +1 -1
- data/lib/rubocop/cop/layout/empty_lines_around_block_body.rb +9 -3
- data/lib/rubocop/cop/layout/end_alignment.rb +5 -15
- data/lib/rubocop/cop/layout/extra_spacing.rb +18 -7
- data/lib/rubocop/cop/layout/first_argument_indentation.rb +26 -1
- data/lib/rubocop/cop/layout/first_array_element_line_break.rb +15 -0
- data/lib/rubocop/cop/layout/first_hash_element_indentation.rb +11 -5
- data/lib/rubocop/cop/layout/heredoc_argument_closing_parenthesis.rb +5 -3
- data/lib/rubocop/cop/layout/indentation_consistency.rb +22 -5
- data/lib/rubocop/cop/layout/line_length.rb +127 -11
- data/lib/rubocop/cop/layout/multiline_hash_brace_layout.rb +6 -4
- data/lib/rubocop/cop/layout/multiline_method_call_indentation.rb +16 -5
- data/lib/rubocop/cop/layout/rescue_ensure_alignment.rb +1 -1
- data/lib/rubocop/cop/layout/single_line_block_chain.rb +3 -3
- data/lib/rubocop/cop/layout/space_around_operators.rb +31 -4
- data/lib/rubocop/cop/layout/space_before_block_braces.rb +12 -0
- data/lib/rubocop/cop/layout/space_inside_block_braces.rb +1 -1
- data/lib/rubocop/cop/layout/space_inside_range_literal.rb +29 -11
- data/lib/rubocop/cop/layout/space_inside_reference_brackets.rb +2 -12
- data/lib/rubocop/cop/lint/ambiguous_regexp_literal.rb +6 -1
- data/lib/rubocop/cop/lint/heredoc_method_call_position.rb +4 -0
- data/lib/rubocop/cop/lint/interpolation_check.rb +20 -17
- data/lib/rubocop/cop/lint/nested_percent_literal.rb +3 -0
- data/lib/rubocop/cop/lint/number_conversion.rb +2 -0
- data/lib/rubocop/cop/lint/parentheses_as_grouped_expression.rb +22 -1
- data/lib/rubocop/cop/lint/percent_string_array.rb +75 -9
- data/lib/rubocop/cop/lint/redundant_cop_disable_directive.rb +27 -8
- data/lib/rubocop/cop/lint/redundant_splat_expansion.rb +11 -4
- data/lib/rubocop/cop/lint/safe_navigation_consistency.rb +47 -1
- data/lib/rubocop/cop/lint/symbol_conversion.rb +1 -0
- data/lib/rubocop/cop/lint/useless_setter_call.rb +7 -4
- data/lib/rubocop/cop/mixin/check_line_breakable.rb +7 -6
- data/lib/rubocop/cop/mixin/check_single_line_suitability.rb +3 -1
- data/lib/rubocop/cop/mixin/comments_help.rb +1 -1
- data/lib/rubocop/cop/mixin/endless_method_body_help.rb +36 -0
- data/lib/rubocop/cop/mixin/hash_alignment_styles.rb +26 -5
- data/lib/rubocop/cop/mixin/statement_modifier.rb +17 -3
- data/lib/rubocop/cop/mixin/trailing_comma.rb +1 -1
- data/lib/rubocop/cop/mixin/visibility_help.rb +2 -1
- data/lib/rubocop/cop/naming/rescued_exceptions_variable_name.rb +1 -1
- data/lib/rubocop/cop/style/access_modifier_declarations.rb +30 -2
- data/lib/rubocop/cop/style/and_or.rb +34 -11
- data/lib/rubocop/cop/style/arguments_forwarding.rb +28 -2
- data/lib/rubocop/cop/style/block_delimiters.rb +6 -0
- data/lib/rubocop/cop/style/case_equality.rb +14 -3
- data/lib/rubocop/cop/style/class_and_module_children.rb +6 -3
- data/lib/rubocop/cop/style/class_methods_definitions.rb +7 -0
- data/lib/rubocop/cop/style/collection_querying.rb +79 -46
- data/lib/rubocop/cop/style/combinable_loops.rb +4 -0
- data/lib/rubocop/cop/style/concat_array_literals.rb +13 -1
- data/lib/rubocop/cop/style/empty_string_inside_interpolation.rb +53 -11
- data/lib/rubocop/cop/style/endless_method.rb +38 -6
- data/lib/rubocop/cop/style/float_division.rb +16 -1
- data/lib/rubocop/cop/style/for.rb +14 -1
- data/lib/rubocop/cop/style/guard_clause.rb +22 -21
- data/lib/rubocop/cop/style/hash_as_last_array_item.rb +4 -0
- data/lib/rubocop/cop/style/hash_lookup_method.rb +10 -0
- data/lib/rubocop/cop/style/if_unless_modifier.rb +19 -12
- data/lib/rubocop/cop/style/it_block_parameter.rb +63 -2
- data/lib/rubocop/cop/style/lambda.rb +16 -1
- data/lib/rubocop/cop/style/method_call_with_args_parentheses/omit_parentheses.rb +36 -4
- data/lib/rubocop/cop/style/method_call_with_args_parentheses.rb +1 -0
- data/lib/rubocop/cop/style/method_def_parentheses.rb +9 -1
- data/lib/rubocop/cop/style/mixin_grouping.rb +12 -3
- data/lib/rubocop/cop/style/mutable_constant.rb +25 -7
- data/lib/rubocop/cop/style/numeric_predicate.rb +7 -0
- data/lib/rubocop/cop/style/parentheses_around_condition.rb +19 -1
- data/lib/rubocop/cop/style/quoted_symbols.rb +5 -7
- data/lib/rubocop/cop/style/raise_args.rb +80 -11
- data/lib/rubocop/cop/style/reduce_to_hash.rb +32 -34
- data/lib/rubocop/cop/style/redundant_begin.rb +26 -3
- data/lib/rubocop/cop/style/redundant_exception.rb +13 -9
- data/lib/rubocop/cop/style/redundant_freeze.rb +1 -2
- data/lib/rubocop/cop/style/redundant_interpolation_unfreeze.rb +24 -1
- data/lib/rubocop/cop/style/redundant_self.rb +9 -1
- data/lib/rubocop/cop/style/redundant_self_assignment_branch.rb +28 -0
- data/lib/rubocop/cop/style/redundant_sort.rb +38 -4
- data/lib/rubocop/cop/style/require_order.rb +16 -8
- data/lib/rubocop/cop/style/rescue_modifier.rb +6 -0
- data/lib/rubocop/cop/style/send_with_literal_method_name.rb +4 -0
- data/lib/rubocop/cop/style/single_line_methods.rb +26 -0
- data/lib/rubocop/cop/style/string_concatenation.rb +12 -1
- data/lib/rubocop/cop/style/struct_inheritance.rb +6 -0
- data/lib/rubocop/cop/style/symbol_proc.rb +1 -1
- data/lib/rubocop/lsp/routes.rb +23 -23
- data/lib/rubocop/plugin/configuration_integrator.rb +24 -3
- data/lib/rubocop/runner.rb +1 -1
- data/lib/rubocop/server/cache.rb +4 -4
- data/lib/rubocop/target_ruby.rb +21 -9
- data/lib/rubocop/version.rb +1 -1
- metadata +2 -2
|
@@ -49,7 +49,7 @@ module RuboCop
|
|
|
49
49
|
MSG = 'Use `%<prefer>s` instead of `%<current>s`.'
|
|
50
50
|
|
|
51
51
|
def self.autocorrect_incompatible_with
|
|
52
|
-
[Style::MethodCallWithArgsParentheses, Style::ReturnNilInPredicateMethodDefinition]
|
|
52
|
+
[Style::MethodCallWithArgsParentheses, Style::ReturnNil, Style::ReturnNilInPredicateMethodDefinition]
|
|
53
53
|
end
|
|
54
54
|
|
|
55
55
|
def on_and(node)
|
|
@@ -72,13 +72,7 @@ module RuboCop
|
|
|
72
72
|
|
|
73
73
|
message = message(node)
|
|
74
74
|
add_offense(node.loc.operator, message: message) do |corrector|
|
|
75
|
-
node.each_child_node
|
|
76
|
-
if expr.send_type?
|
|
77
|
-
correct_send(expr, corrector)
|
|
78
|
-
elsif expr.type?(:return, :next, :break, :yield) || expr.assignment?
|
|
79
|
-
correct_other(expr, corrector)
|
|
80
|
-
end
|
|
81
|
-
end
|
|
75
|
+
node.each_child_node { |expr| correct_operand(expr, corrector) }
|
|
82
76
|
|
|
83
77
|
corrector.replace(node.loc.operator, node.alternate_operator)
|
|
84
78
|
|
|
@@ -86,6 +80,22 @@ module RuboCop
|
|
|
86
80
|
end
|
|
87
81
|
end
|
|
88
82
|
|
|
83
|
+
def correct_operand(expr, corrector)
|
|
84
|
+
expr = expr.send_node if expr.any_block_type?
|
|
85
|
+
|
|
86
|
+
if expr.type?(:call, :super)
|
|
87
|
+
correct_send(expr, corrector)
|
|
88
|
+
elsif wrap_in_parentheses?(expr)
|
|
89
|
+
correct_other(expr, corrector)
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# These bind looser than `&&` and `||`, or can't be their operands at all.
|
|
94
|
+
def wrap_in_parentheses?(node)
|
|
95
|
+
node.type?(:return, :next, :break, :yield, :any_match_pattern, :range, :defined?) ||
|
|
96
|
+
node.assignment? || (node.if_type? && node.ternary?)
|
|
97
|
+
end
|
|
98
|
+
|
|
89
99
|
def on_conditionals(node)
|
|
90
100
|
node.condition.each_node(*AST::Node::OPERATOR_KEYWORDS) do |operator|
|
|
91
101
|
process_logical_operator(operator)
|
|
@@ -101,6 +111,7 @@ module RuboCop
|
|
|
101
111
|
return correct_setter(node, corrector) if node.setter_method?
|
|
102
112
|
return correct_other(node, corrector) if node.comparison_method?
|
|
103
113
|
|
|
114
|
+
correct_block_receiver(node, corrector)
|
|
104
115
|
return unless correctable_send?(node)
|
|
105
116
|
|
|
106
117
|
whitespace_before_arg_range = whitespace_before_arg(node)
|
|
@@ -120,7 +131,8 @@ module RuboCop
|
|
|
120
131
|
# however, 'not x' also parses as (send x :!)
|
|
121
132
|
def correct_not(node, receiver, corrector)
|
|
122
133
|
if node.prefix_bang?
|
|
123
|
-
|
|
134
|
+
receiver = receiver.send_node if receiver.any_block_type?
|
|
135
|
+
return unless receiver.type?(:call, :super)
|
|
124
136
|
|
|
125
137
|
correct_send(receiver, corrector)
|
|
126
138
|
elsif node.prefix_not?
|
|
@@ -145,12 +157,23 @@ module RuboCop
|
|
|
145
157
|
end
|
|
146
158
|
end
|
|
147
159
|
|
|
160
|
+
# A method chained onto a `do` block, as in `bar baz do ... end.qux`,
|
|
161
|
+
# still needs parentheses around the arguments of the call that takes the block.
|
|
162
|
+
def correct_block_receiver(node, corrector)
|
|
163
|
+
receiver = node.receiver
|
|
164
|
+
receiver = receiver.receiver while receiver&.call_type?
|
|
165
|
+
correct_operand(receiver, corrector) if receiver&.any_block_type?
|
|
166
|
+
end
|
|
167
|
+
|
|
148
168
|
def correctable_send?(node)
|
|
149
|
-
|
|
169
|
+
# Operator syntax such as `a + b` or `a[b]` already binds tighter than `&&` and `||`.
|
|
170
|
+
return false if node.operator_method? && !node.loc.dot
|
|
171
|
+
|
|
172
|
+
!node.parenthesized? && node.arguments?
|
|
150
173
|
end
|
|
151
174
|
|
|
152
175
|
def whitespace_before_arg(node)
|
|
153
|
-
begin_paren = node.
|
|
176
|
+
begin_paren = node.selector.end_pos
|
|
154
177
|
end_paren = begin_paren
|
|
155
178
|
# Increment position of parenthesis, unless message is a predicate
|
|
156
179
|
# method followed by a non-whitespace char (e.g. is_a?String).
|
|
@@ -169,7 +169,7 @@ module RuboCop
|
|
|
169
169
|
|
|
170
170
|
return if send_classifications.empty?
|
|
171
171
|
|
|
172
|
-
if
|
|
172
|
+
if forward_all?(send_nodes, send_classifications)
|
|
173
173
|
add_forward_all_offenses(node, send_classifications, forwardable_args)
|
|
174
174
|
elsif target_ruby_version >= 3.2
|
|
175
175
|
add_post_ruby_32_offenses(node, send_classifications, forwardable_args)
|
|
@@ -192,11 +192,33 @@ module RuboCop
|
|
|
192
192
|
[restarg_node, kwrestarg_node, blockarg_node]
|
|
193
193
|
end
|
|
194
194
|
|
|
195
|
+
def forward_all?(send_nodes, send_classifications)
|
|
196
|
+
only_forwards_all?(send_classifications) &&
|
|
197
|
+
!anonymous_forwarding_elsewhere?(send_nodes, send_classifications)
|
|
198
|
+
end
|
|
199
|
+
|
|
195
200
|
def only_forwards_all?(send_classifications)
|
|
196
201
|
all_classifications = %i[all all_anonymous].freeze
|
|
197
202
|
send_classifications.all? { |_, c, _, _| all_classifications.include?(c) }
|
|
198
203
|
end
|
|
199
204
|
|
|
205
|
+
def anonymous_forwarding_elsewhere?(send_nodes, send_classifications)
|
|
206
|
+
classified = send_classifications.map(&:first)
|
|
207
|
+
|
|
208
|
+
send_nodes.any? do |send_node|
|
|
209
|
+
classified.none? { |node| node.equal?(send_node) } &&
|
|
210
|
+
forwards_anonymous_args?(send_node)
|
|
211
|
+
end
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
def forwards_anonymous_args?(send_node)
|
|
215
|
+
send_node.arguments.any? do |arg|
|
|
216
|
+
next arg.children.any?(&:forwarded_kwrestarg_type?) if arg.hash_type?
|
|
217
|
+
|
|
218
|
+
arg.forwarded_restarg_type?
|
|
219
|
+
end
|
|
220
|
+
end
|
|
221
|
+
|
|
200
222
|
# rubocop:disable-next Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
|
|
201
223
|
def add_forward_all_offenses(node, send_classifications, forwardable_args)
|
|
202
224
|
rest_arg, kwrest_arg, block_arg = *forwardable_args
|
|
@@ -376,12 +398,16 @@ module RuboCop
|
|
|
376
398
|
|
|
377
399
|
def add_parens_if_missing(node, corrector)
|
|
378
400
|
return if parentheses?(node)
|
|
379
|
-
return if node.
|
|
401
|
+
return if node.call_type? && operator_or_setter_call?(node)
|
|
380
402
|
return unless (@parenthesized_nodes ||= Set.new.compare_by_identity).add?(node)
|
|
381
403
|
|
|
382
404
|
add_parentheses(node, corrector)
|
|
383
405
|
end
|
|
384
406
|
|
|
407
|
+
def operator_or_setter_call?(node)
|
|
408
|
+
node.setter_method? || (node.operator_method? && !node.loc.dot)
|
|
409
|
+
end
|
|
410
|
+
|
|
385
411
|
# Classifies send nodes for possible rest/kwrest/all (including block) forwarding.
|
|
386
412
|
class SendNodeClassifier
|
|
387
413
|
extend NodePattern::Macros
|
|
@@ -306,6 +306,8 @@ module RuboCop
|
|
|
306
306
|
end
|
|
307
307
|
|
|
308
308
|
def move_comment_before_block(corrector, comment, block_node, closing_brace)
|
|
309
|
+
return if directive_comment?(comment)
|
|
310
|
+
|
|
309
311
|
range = block_node.chained? ? end_of_chain(block_node.parent).source_range : closing_brace
|
|
310
312
|
|
|
311
313
|
# It is possible that there is code between the block and the comment
|
|
@@ -319,6 +321,10 @@ module RuboCop
|
|
|
319
321
|
corrector.insert_before(block_node, "#{comment.text}\n")
|
|
320
322
|
end
|
|
321
323
|
|
|
324
|
+
def directive_comment?(comment)
|
|
325
|
+
comment.text.match?(DirectiveComment::DIRECTIVE_COMMENT_REGEXP)
|
|
326
|
+
end
|
|
327
|
+
|
|
322
328
|
def source_range_before_comment(range, comment)
|
|
323
329
|
range = range.end.join(comment.source_range.begin)
|
|
324
330
|
|
|
@@ -7,7 +7,7 @@ module RuboCop
|
|
|
7
7
|
# The `===` operator has different behavior depending on the
|
|
8
8
|
# receiver and its use outside of `case`/`when` is confusing.
|
|
9
9
|
# Prefer more explicit alternatives like `is_a?`, `include?`,
|
|
10
|
-
# or `match?`.
|
|
10
|
+
# `cover?`, or `match?`.
|
|
11
11
|
#
|
|
12
12
|
# If `AllowOnConstant` option is enabled, the cop will ignore violations when the receiver of
|
|
13
13
|
# the case equality operator is a constant.
|
|
@@ -22,9 +22,11 @@ module RuboCop
|
|
|
22
22
|
# @example
|
|
23
23
|
# # bad
|
|
24
24
|
# (1..100) === 7
|
|
25
|
+
# ('a'..'z') === char
|
|
25
26
|
#
|
|
26
27
|
# # good
|
|
27
28
|
# (1..100).include?(7)
|
|
29
|
+
# ('a'..'z').cover?(char)
|
|
28
30
|
#
|
|
29
31
|
# @example AllowOnConstant: false (default)
|
|
30
32
|
# # bad
|
|
@@ -90,9 +92,18 @@ module RuboCop
|
|
|
90
92
|
end
|
|
91
93
|
|
|
92
94
|
def begin_replacement(lhs, rhs)
|
|
93
|
-
|
|
95
|
+
range = lhs.children.first
|
|
96
|
+
return unless range&.range_type?
|
|
94
97
|
|
|
95
|
-
|
|
98
|
+
method = cover_semantics?(range) ? 'cover?' : 'include?'
|
|
99
|
+
|
|
100
|
+
"#{lhs.source}.#{method}(#{rhs.source})"
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# `Range#===` behaves like `Range#cover?` since Ruby 2.7 (since Ruby 2.6 for ranges
|
|
104
|
+
# that aren't string ranges), and `Range#include?` only matches that for numeric ranges.
|
|
105
|
+
def cover_semantics?(range)
|
|
106
|
+
target_ruby_version >= 2.7 && !range.children.compact.all?(&:numeric_type?)
|
|
96
107
|
end
|
|
97
108
|
|
|
98
109
|
def const_replacement(lhs, rhs)
|
|
@@ -65,10 +65,11 @@ module RuboCop
|
|
|
65
65
|
NESTED_MSG = 'Use nested module/class definitions instead of compact style.'
|
|
66
66
|
COMPACT_MSG = 'Use compact module/class definition instead of nested style.'
|
|
67
67
|
|
|
68
|
-
# `Style/StructInheritance`
|
|
69
|
-
# the same pass, which the compacted `class` keyword would
|
|
68
|
+
# `Style/StructInheritance` and `Style/DataInheritance` turn a class definition into
|
|
69
|
+
# a constant assignment in the same pass, which the compacted `class` keyword would
|
|
70
|
+
# then precede.
|
|
70
71
|
def self.autocorrect_incompatible_with
|
|
71
|
-
[Style::StructInheritance]
|
|
72
|
+
[Style::DataInheritance, Style::StructInheritance]
|
|
72
73
|
end
|
|
73
74
|
|
|
74
75
|
def on_class(node)
|
|
@@ -152,7 +153,9 @@ module RuboCop
|
|
|
152
153
|
# making autocorrection ping-pong between the two forms.
|
|
153
154
|
return if style_for_kind(node.body.type) == :nested
|
|
154
155
|
return unless compactible_namespace?(node)
|
|
156
|
+
return if node.each_ancestor(:class, :module).any? { |ancestor| ignored_node?(ancestor) }
|
|
155
157
|
|
|
158
|
+
ignore_node(node)
|
|
156
159
|
compact_node(corrector, node)
|
|
157
160
|
remove_end(corrector, node.body)
|
|
158
161
|
unindent(corrector, node)
|
|
@@ -75,7 +75,10 @@ module RuboCop
|
|
|
75
75
|
return unless def_nodes(node).all? { |def_node| no_code_after?(def_node.source_range) }
|
|
76
76
|
|
|
77
77
|
add_offense(node, message: MSG_SCLASS) do |corrector|
|
|
78
|
+
next if inside_corrected_sclass?(node)
|
|
79
|
+
|
|
78
80
|
autocorrect_sclass(node, corrector)
|
|
81
|
+
ignore_node(node)
|
|
79
82
|
end
|
|
80
83
|
end
|
|
81
84
|
|
|
@@ -120,6 +123,10 @@ module RuboCop
|
|
|
120
123
|
trailing.empty? || trailing.start_with?('#')
|
|
121
124
|
end
|
|
122
125
|
|
|
126
|
+
def inside_corrected_sclass?(node)
|
|
127
|
+
node.each_ancestor(:sclass).any? { |ancestor| ignored_node?(ancestor) }
|
|
128
|
+
end
|
|
129
|
+
|
|
123
130
|
def autocorrect_sclass(node, corrector)
|
|
124
131
|
rewritten_defs = []
|
|
125
132
|
|
|
@@ -15,31 +15,24 @@ module RuboCop
|
|
|
15
15
|
# would yield false positives. For example, `String` implements `length`
|
|
16
16
|
# and `size`, but it doesn't include `Enumerable`.
|
|
17
17
|
#
|
|
18
|
+
# When `count` is used without a block, it measures the collection's
|
|
19
|
+
# size, so the offense is corrected to `empty?` or `!empty?`. The
|
|
20
|
+
# predicate methods `any?`, `none?` and `one?` test element truthiness
|
|
21
|
+
# instead and would return different results for collections with falsey
|
|
22
|
+
# elements (e.g. `[false].count.zero?` is `false`, but `[false].none?`
|
|
23
|
+
# is `true`). For the same reason, `count == 1` and `count > 1` without
|
|
24
|
+
# a block are not flagged.
|
|
25
|
+
#
|
|
18
26
|
# @safety
|
|
19
27
|
# The cop is unsafe because receiver might not include `Enumerable`, or
|
|
20
28
|
# it has nonstandard implementation of `count` or any replacement
|
|
21
29
|
# methods.
|
|
22
30
|
#
|
|
23
|
-
#
|
|
24
|
-
#
|
|
25
|
-
# `
|
|
26
|
-
#
|
|
27
|
-
#
|
|
28
|
-
# ----
|
|
29
|
-
# [nil, false].count.positive?
|
|
30
|
-
# [nil].count == 1
|
|
31
|
-
# # => true
|
|
32
|
-
#
|
|
33
|
-
# [nil, false].any?
|
|
34
|
-
# [nil].one?
|
|
35
|
-
# # => false
|
|
36
|
-
#
|
|
37
|
-
# [nil].count == 0
|
|
38
|
-
# # => false
|
|
39
|
-
#
|
|
40
|
-
# [nil].none?
|
|
41
|
-
# # => true
|
|
42
|
-
# ----
|
|
31
|
+
# Blockless `count` is corrected to `empty?`, which `Enumerable` doesn't
|
|
32
|
+
# define. `Range`, `Enumerator` (including lazy ones), `Struct`, `StringIO`
|
|
33
|
+
# and any class that just includes `Enumerable` respond to `count` but not
|
|
34
|
+
# to `empty?`, so `(1...1).count.zero?` is `true` while `(1...1).empty?`
|
|
35
|
+
# raises `NoMethodError`.
|
|
43
36
|
#
|
|
44
37
|
# Autocorrection is unsafe when replacement methods don't iterate over
|
|
45
38
|
# every element in collection and the given block runs side effects:
|
|
@@ -60,12 +53,14 @@ module RuboCop
|
|
|
60
53
|
# x.count > 0
|
|
61
54
|
# x.count != 0
|
|
62
55
|
#
|
|
56
|
+
# # good
|
|
57
|
+
# !x.empty?
|
|
58
|
+
#
|
|
59
|
+
# # bad
|
|
63
60
|
# x.count(&:foo?).positive?
|
|
64
61
|
# x.count { |item| item.foo? }.positive?
|
|
65
62
|
#
|
|
66
63
|
# # good
|
|
67
|
-
# x.any?
|
|
68
|
-
#
|
|
69
64
|
# x.any?(&:foo?)
|
|
70
65
|
# x.any? { |item| item.foo? }
|
|
71
66
|
#
|
|
@@ -74,24 +69,33 @@ module RuboCop
|
|
|
74
69
|
# x.count == 0
|
|
75
70
|
#
|
|
76
71
|
# # good
|
|
77
|
-
# x.
|
|
72
|
+
# x.empty?
|
|
78
73
|
#
|
|
79
74
|
# # bad
|
|
80
|
-
# x.count
|
|
81
|
-
# x.
|
|
75
|
+
# x.count(&:foo?).zero?
|
|
76
|
+
# x.count(&:foo?) == 0
|
|
77
|
+
#
|
|
78
|
+
# # good
|
|
79
|
+
# x.none?(&:foo?)
|
|
80
|
+
#
|
|
81
|
+
# # bad
|
|
82
|
+
# x.count(&:foo?) == 1
|
|
83
|
+
#
|
|
84
|
+
# # good
|
|
85
|
+
# x.one?(&:foo?)
|
|
82
86
|
#
|
|
83
87
|
# @example AllCops:ActiveSupportExtensionsEnabled: false (default)
|
|
84
88
|
#
|
|
85
89
|
# # good
|
|
86
|
-
# x.count > 1
|
|
90
|
+
# x.count(&:foo?) > 1
|
|
87
91
|
#
|
|
88
92
|
# @example AllCops:ActiveSupportExtensionsEnabled: true
|
|
89
93
|
#
|
|
90
94
|
# # bad
|
|
91
|
-
# x.count > 1
|
|
95
|
+
# x.count(&:foo?) > 1
|
|
92
96
|
#
|
|
93
97
|
# # good
|
|
94
|
-
# x.many?
|
|
98
|
+
# x.many?(&:foo?)
|
|
95
99
|
#
|
|
96
100
|
class CollectionQuerying < Base
|
|
97
101
|
include RangeHelp
|
|
@@ -102,13 +106,21 @@ module RuboCop
|
|
|
102
106
|
RESTRICT_ON_SEND = %i[positive? > != zero? ==].freeze
|
|
103
107
|
|
|
104
108
|
REPLACEMENTS = {
|
|
105
|
-
[:positive?, nil] =>
|
|
106
|
-
[:>, 0] =>
|
|
107
|
-
[:!=, 0] =>
|
|
108
|
-
[:zero?, nil] =>
|
|
109
|
-
[:==, 0] =>
|
|
110
|
-
[:==, 1] =>
|
|
111
|
-
[:>, 1] =>
|
|
109
|
+
[:positive?, nil] => 'any?',
|
|
110
|
+
[:>, 0] => 'any?',
|
|
111
|
+
[:!=, 0] => 'any?',
|
|
112
|
+
[:zero?, nil] => 'none?',
|
|
113
|
+
[:==, 0] => 'none?',
|
|
114
|
+
[:==, 1] => 'one?',
|
|
115
|
+
[:>, 1] => 'many?'
|
|
116
|
+
}.freeze
|
|
117
|
+
|
|
118
|
+
NO_BLOCK_REPLACEMENTS = {
|
|
119
|
+
[:positive?, nil] => '!empty?',
|
|
120
|
+
[:>, 0] => '!empty?',
|
|
121
|
+
[:!=, 0] => '!empty?',
|
|
122
|
+
[:zero?, nil] => 'empty?',
|
|
123
|
+
[:==, 0] => 'empty?'
|
|
112
124
|
}.freeze
|
|
113
125
|
|
|
114
126
|
# @!method count_predicate(node)
|
|
@@ -132,28 +144,49 @@ module RuboCop
|
|
|
132
144
|
def on_send(node)
|
|
133
145
|
return unless (count_node = count_predicate(node))
|
|
134
146
|
|
|
135
|
-
|
|
147
|
+
replacement = replacement_method(node, count_node)
|
|
136
148
|
|
|
137
|
-
return unless replacement_supported?(
|
|
149
|
+
return unless replacement_supported?(node, replacement)
|
|
138
150
|
|
|
139
151
|
offense_range = count_node.loc.selector.join(node.source_range.end)
|
|
140
|
-
add_offense(offense_range,
|
|
141
|
-
|
|
142
|
-
corrector.replace(count_node.loc.selector, replacement_method)
|
|
143
|
-
corrector.remove(removal_range(node))
|
|
152
|
+
add_offense(offense_range, message: format(MSG, prefer: replacement)) do |corrector|
|
|
153
|
+
autocorrect(corrector, node, count_node, replacement)
|
|
144
154
|
end
|
|
145
155
|
end
|
|
146
156
|
|
|
147
157
|
private
|
|
148
158
|
|
|
149
|
-
def replacement_method(node)
|
|
150
|
-
|
|
159
|
+
def replacement_method(node, count_node)
|
|
160
|
+
key = [node.method_name, node.first_argument&.value]
|
|
161
|
+
return REPLACEMENTS.fetch(key) if block_given_to_count?(count_node)
|
|
162
|
+
|
|
163
|
+
NO_BLOCK_REPLACEMENTS[key]
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
def block_given_to_count?(count_node)
|
|
167
|
+
count_node.parent&.any_block_type? || count_node.last_argument&.block_pass_type?
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
def replacement_supported?(node, replacement)
|
|
171
|
+
return false unless replacement
|
|
172
|
+
return false if replacement == 'many?' && !active_support_extensions_enabled?
|
|
173
|
+
|
|
174
|
+
!(replacement.start_with?('!') && receiver_of_call?(node))
|
|
151
175
|
end
|
|
152
176
|
|
|
153
|
-
def
|
|
154
|
-
|
|
177
|
+
def receiver_of_call?(node)
|
|
178
|
+
node.parent&.call_type? && node.parent.receiver == node && node.parent.loc.dot
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
def autocorrect(corrector, node, count_node, replacement)
|
|
182
|
+
if replacement.start_with?('!')
|
|
183
|
+
corrector.insert_before(count_node, '!')
|
|
184
|
+
corrector.replace(count_node.loc.selector, 'empty?')
|
|
185
|
+
else
|
|
186
|
+
corrector.replace(count_node.loc.selector, replacement)
|
|
187
|
+
end
|
|
155
188
|
|
|
156
|
-
|
|
189
|
+
corrector.remove(removal_range(node))
|
|
157
190
|
end
|
|
158
191
|
|
|
159
192
|
def removal_range(node)
|
|
@@ -65,6 +65,10 @@ module RuboCop
|
|
|
65
65
|
|
|
66
66
|
MSG = 'Combine this loop with the previous loop.'
|
|
67
67
|
|
|
68
|
+
def self.autocorrect_incompatible_with
|
|
69
|
+
[Layout::BlockEndNewline, Layout::RedundantLineBreak]
|
|
70
|
+
end
|
|
71
|
+
|
|
68
72
|
def on_block(node)
|
|
69
73
|
return unless node.parent&.begin_type?
|
|
70
74
|
return unless collection_looping_method?(node)
|
|
@@ -118,7 +118,8 @@ module RuboCop
|
|
|
118
118
|
# A comment or a heredoc body there can't be moved out of the way.
|
|
119
119
|
return unless closing_ranges.all? { |range| range.source.match?(/\A[\s,]*\]\z/) }
|
|
120
120
|
|
|
121
|
-
corrector
|
|
121
|
+
replace_selector(corrector, node)
|
|
122
|
+
|
|
122
123
|
node.arguments.zip(closing_ranges) do |argument, closing_range|
|
|
123
124
|
corrector.remove(argument.loc.begin)
|
|
124
125
|
corrector.remove(closing_range)
|
|
@@ -133,6 +134,17 @@ module RuboCop
|
|
|
133
134
|
|
|
134
135
|
range_between(argument.children.last.source_range.end_pos, argument.loc.end.end_pos)
|
|
135
136
|
end
|
|
137
|
+
|
|
138
|
+
def replace_selector(corrector, node)
|
|
139
|
+
if node.parenthesized? || node.single_line?
|
|
140
|
+
corrector.replace(node.loc.selector, 'push')
|
|
141
|
+
else
|
|
142
|
+
range = node.loc.selector.join(node.first_argument.source_range.begin)
|
|
143
|
+
|
|
144
|
+
corrector.replace(range, 'push(')
|
|
145
|
+
corrector.insert_after(node.last_argument.loc.end, ')')
|
|
146
|
+
end
|
|
147
|
+
end
|
|
136
148
|
end
|
|
137
149
|
end
|
|
138
150
|
end
|
|
@@ -45,6 +45,8 @@ module RuboCop
|
|
|
45
45
|
MSG_TRAILING_CONDITIONAL = 'Do not use trailing conditionals in string interpolation.'
|
|
46
46
|
MSG_TERNARY = 'Do not return empty strings in string interpolation.'
|
|
47
47
|
|
|
48
|
+
INTERPOLATED_TYPES = %i[dstr dsym regexp xstr].freeze
|
|
49
|
+
|
|
48
50
|
def on_interpolation(node)
|
|
49
51
|
node.each_child_node(:if) do |child_node|
|
|
50
52
|
if style == :trailing_conditional
|
|
@@ -63,28 +65,54 @@ module RuboCop
|
|
|
63
65
|
return if child_node.modifier_form?
|
|
64
66
|
|
|
65
67
|
if empty_if_outcome?(child_node)
|
|
66
|
-
ternary_style_autocorrect(child_node, child_node.else_branch
|
|
68
|
+
ternary_style_autocorrect(child_node, child_node.else_branch, 'unless')
|
|
67
69
|
end
|
|
68
70
|
|
|
69
71
|
return unless empty_else_outcome?(child_node)
|
|
70
72
|
|
|
71
|
-
ternary_style_autocorrect(child_node, child_node.if_branch
|
|
73
|
+
ternary_style_autocorrect(child_node, child_node.if_branch, 'if')
|
|
72
74
|
end
|
|
73
75
|
|
|
74
76
|
def ternary_correction(node, child_node)
|
|
75
77
|
return unless child_node.modifier_form?
|
|
76
78
|
|
|
77
|
-
ternary_component = if child_node.unless?
|
|
78
|
-
"'' : #{child_node.if_branch.source}"
|
|
79
|
-
else
|
|
80
|
-
"#{child_node.if_branch.source} : ''"
|
|
81
|
-
end
|
|
82
|
-
|
|
83
79
|
add_offense(node, message: MSG_TRAILING_CONDITIONAL) do |corrector|
|
|
84
|
-
|
|
80
|
+
next if nested_trailing_conditional?(child_node)
|
|
81
|
+
|
|
82
|
+
autocorrect_to_ternary(corrector, child_node)
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def nested_trailing_conditional?(if_node)
|
|
87
|
+
if_node.each_descendant(:if).any? do |node|
|
|
88
|
+
node.modifier_form? && node.parent.parent&.type?(*INTERPOLATED_TYPES)
|
|
85
89
|
end
|
|
86
90
|
end
|
|
87
91
|
|
|
92
|
+
def autocorrect_to_ternary(corrector, if_node)
|
|
93
|
+
condition = if_node.condition.source
|
|
94
|
+
branch = ternary_branch_source(if_node.if_branch)
|
|
95
|
+
outcomes = if_node.unless? ? "'' : #{branch}" : "#{branch} : ''"
|
|
96
|
+
|
|
97
|
+
corrector.replace(if_node, "#{condition} ? #{outcomes}")
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def ternary_branch_source(branch)
|
|
101
|
+
return branch.source unless command_call?(branch)
|
|
102
|
+
|
|
103
|
+
arguments = branch.first_argument.source_range.join(branch.last_argument.source_range)
|
|
104
|
+
method = branch.source_range.begin.join(arguments.begin).source.rstrip
|
|
105
|
+
|
|
106
|
+
"#{method}(#{arguments.source})"
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def command_call?(node)
|
|
110
|
+
return false unless node.type?(:call, :super, :yield) && node.arguments?
|
|
111
|
+
return false if node.call_type? && (node.operator_method? || node.setter_method?)
|
|
112
|
+
|
|
113
|
+
!node.parenthesized?
|
|
114
|
+
end
|
|
115
|
+
|
|
88
116
|
def empty_if_outcome?(node)
|
|
89
117
|
empty_branch_outcome?(node.if_branch)
|
|
90
118
|
end
|
|
@@ -99,9 +127,23 @@ module RuboCop
|
|
|
99
127
|
branch.nil_type? || (branch.str_type? && branch.value.empty?)
|
|
100
128
|
end
|
|
101
129
|
|
|
102
|
-
def ternary_style_autocorrect(node, outcome,
|
|
130
|
+
def ternary_style_autocorrect(node, outcome, keyword)
|
|
103
131
|
add_offense(node, message: MSG_TERNARY) do |corrector|
|
|
104
|
-
|
|
132
|
+
next if empty_string_conditional_in_condition?(node)
|
|
133
|
+
|
|
134
|
+
head = node.source_range.begin.join(outcome.source_range.begin)
|
|
135
|
+
tail = outcome.source_range.end.join(node.source_range.end)
|
|
136
|
+
|
|
137
|
+
corrector.remove(head)
|
|
138
|
+
corrector.replace(tail, " #{keyword} #{node.condition.source}")
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
def empty_string_conditional_in_condition?(if_node)
|
|
143
|
+
if_node.condition.each_descendant(:if).any? do |node|
|
|
144
|
+
next false if node.modifier_form? || !node.parent.parent&.type?(*INTERPOLATED_TYPES)
|
|
145
|
+
|
|
146
|
+
empty_if_outcome?(node) || empty_else_outcome?(node)
|
|
105
147
|
end
|
|
106
148
|
end
|
|
107
149
|
end
|
|
@@ -147,8 +147,21 @@ module RuboCop
|
|
|
147
147
|
|
|
148
148
|
# `Style/MethodDefParentheses` adds the same parentheses a parameter list needs in
|
|
149
149
|
# an endless definition, so letting both correct in the same pass would double them.
|
|
150
|
+
# `Style/GuardClause` turns a single `if` body into several statements,
|
|
151
|
+
# which an endless definition cannot hold.
|
|
152
|
+
# `Style/RaiseArgs` would drop the parentheses of a `raise` that becomes
|
|
153
|
+
# the body in the same pass, which a method passed as an argument needs.
|
|
154
|
+
# `Style/IfUnlessModifier` and `Style/WhileUntilModifier` would turn the body into
|
|
155
|
+
# a modifier form, which then applies to the whole endless definition.
|
|
150
156
|
def self.autocorrect_incompatible_with
|
|
151
|
-
[
|
|
157
|
+
[
|
|
158
|
+
Style::GuardClause,
|
|
159
|
+
Style::IfUnlessModifier,
|
|
160
|
+
Style::MethodCallWithArgsParentheses,
|
|
161
|
+
Style::MethodDefParentheses,
|
|
162
|
+
Style::RaiseArgs,
|
|
163
|
+
Style::WhileUntilModifier
|
|
164
|
+
]
|
|
152
165
|
end
|
|
153
166
|
|
|
154
167
|
def on_def(node)
|
|
@@ -185,9 +198,7 @@ module RuboCop
|
|
|
185
198
|
add_offense(node, message: MSG_MULTI_LINE) do |corrector|
|
|
186
199
|
correct_to_multiline(corrector, node)
|
|
187
200
|
end
|
|
188
|
-
elsif
|
|
189
|
-
return if too_long_when_made_endless?(node)
|
|
190
|
-
|
|
201
|
+
elsif convertible_to_endless?(node) && single_line_when_made_endless?(node)
|
|
191
202
|
add_offense(node, message: MSG_REQUIRE_SINGLE) do |corrector|
|
|
192
203
|
correct_to_endless(corrector, node)
|
|
193
204
|
end
|
|
@@ -195,14 +206,18 @@ module RuboCop
|
|
|
195
206
|
end
|
|
196
207
|
|
|
197
208
|
def handle_require_always_style(node)
|
|
198
|
-
return
|
|
199
|
-
return if too_long_when_made_endless?(node)
|
|
209
|
+
return unless convertible_to_endless?(node)
|
|
200
210
|
|
|
201
211
|
add_offense(node, message: MSG_REQUIRE_ALWAYS) do |corrector|
|
|
202
212
|
correct_to_endless(corrector, node)
|
|
203
213
|
end
|
|
204
214
|
end
|
|
205
215
|
|
|
216
|
+
def convertible_to_endless?(node)
|
|
217
|
+
!node.endless? && can_be_made_endless?(node) &&
|
|
218
|
+
!too_long_when_made_endless?(node) && !ambiguous_when_made_endless?(node)
|
|
219
|
+
end
|
|
220
|
+
|
|
206
221
|
def handle_disallow_style(node)
|
|
207
222
|
return unless node.endless?
|
|
208
223
|
|
|
@@ -293,6 +308,23 @@ module RuboCop
|
|
|
293
308
|
!endless_replacement(node).include?("\n")
|
|
294
309
|
end
|
|
295
310
|
|
|
311
|
+
def ambiguous_when_made_endless?(node)
|
|
312
|
+
(operation = ambiguous_operation(node)) && ambiguous_enclosing_operation?(operation)
|
|
313
|
+
end
|
|
314
|
+
|
|
315
|
+
# @!method ambiguous_operation(node)
|
|
316
|
+
def_node_matcher :ambiguous_operation, <<~PATTERN
|
|
317
|
+
^${
|
|
318
|
+
(if _ <any_def _>)
|
|
319
|
+
({and or} any_def _)
|
|
320
|
+
({while until} _ any_def)
|
|
321
|
+
}
|
|
322
|
+
PATTERN
|
|
323
|
+
|
|
324
|
+
def ambiguous_enclosing_operation?(operation)
|
|
325
|
+
operation.operator_keyword? || operation.modifier_form?
|
|
326
|
+
end
|
|
327
|
+
|
|
296
328
|
def too_long_when_made_endless?(node)
|
|
297
329
|
return false unless config.cop_enabled?('Layout/LineLength')
|
|
298
330
|
|