rubocop 1.75.5 → 1.77.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.
Files changed (89) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +20 -14
  3. data/config/default.yml +74 -7
  4. data/config/obsoletion.yml +6 -3
  5. data/lib/rubocop/cop/autocorrect_logic.rb +18 -10
  6. data/lib/rubocop/cop/bundler/ordered_gems.rb +1 -1
  7. data/lib/rubocop/cop/correctors/parentheses_corrector.rb +5 -2
  8. data/lib/rubocop/cop/gemspec/attribute_assignment.rb +91 -0
  9. data/lib/rubocop/cop/gemspec/duplicated_assignment.rb +37 -15
  10. data/lib/rubocop/cop/gemspec/ordered_dependencies.rb +1 -1
  11. data/lib/rubocop/cop/gemspec/require_mfa.rb +15 -1
  12. data/lib/rubocop/cop/internal_affairs/node_matcher_directive.rb +4 -4
  13. data/lib/rubocop/cop/internal_affairs/node_pattern_groups.rb +1 -0
  14. data/lib/rubocop/cop/layout/class_structure.rb +35 -0
  15. data/lib/rubocop/cop/layout/closing_parenthesis_indentation.rb +1 -1
  16. data/lib/rubocop/cop/layout/empty_lines_around_access_modifier.rb +7 -3
  17. data/lib/rubocop/cop/layout/first_argument_indentation.rb +1 -1
  18. data/lib/rubocop/cop/layout/line_length.rb +26 -5
  19. data/lib/rubocop/cop/layout/space_before_brackets.rb +5 -38
  20. data/lib/rubocop/cop/layout/space_inside_array_literal_brackets.rb +8 -2
  21. data/lib/rubocop/cop/lint/ambiguous_range.rb +5 -0
  22. data/lib/rubocop/cop/lint/deprecated_class_methods.rb +1 -1
  23. data/lib/rubocop/cop/lint/duplicate_methods.rb +84 -2
  24. data/lib/rubocop/cop/lint/empty_interpolation.rb +3 -1
  25. data/lib/rubocop/cop/lint/float_comparison.rb +31 -4
  26. data/lib/rubocop/cop/lint/identity_comparison.rb +19 -15
  27. data/lib/rubocop/cop/lint/literal_as_condition.rb +19 -27
  28. data/lib/rubocop/cop/lint/redundant_regexp_quantifiers.rb +1 -1
  29. data/lib/rubocop/cop/lint/safe_navigation_chain.rb +4 -4
  30. data/lib/rubocop/cop/lint/self_assignment.rb +25 -0
  31. data/lib/rubocop/cop/lint/shadowing_outer_local_variable.rb +5 -0
  32. data/lib/rubocop/cop/lint/useless_access_modifier.rb +29 -4
  33. data/lib/rubocop/cop/lint/useless_assignment.rb +2 -0
  34. data/lib/rubocop/cop/lint/useless_default_value_argument.rb +90 -0
  35. data/lib/rubocop/cop/lint/useless_or.rb +98 -0
  36. data/lib/rubocop/cop/lint/useless_ruby2_keywords.rb +3 -3
  37. data/lib/rubocop/cop/metrics/abc_size.rb +1 -1
  38. data/lib/rubocop/cop/mixin/alignment.rb +1 -1
  39. data/lib/rubocop/cop/mixin/frozen_string_literal.rb +1 -1
  40. data/lib/rubocop/cop/mixin/gemspec_help.rb +22 -0
  41. data/lib/rubocop/cop/mixin/line_length_help.rb +24 -8
  42. data/lib/rubocop/cop/mixin/ordered_gem_node.rb +1 -1
  43. data/lib/rubocop/cop/naming/file_name.rb +2 -2
  44. data/lib/rubocop/cop/naming/predicate_method.rb +281 -0
  45. data/lib/rubocop/cop/naming/{predicate_name.rb → predicate_prefix.rb} +4 -4
  46. data/lib/rubocop/cop/style/access_modifier_declarations.rb +32 -10
  47. data/lib/rubocop/cop/style/case_like_if.rb +1 -1
  48. data/lib/rubocop/cop/style/collection_querying.rb +167 -0
  49. data/lib/rubocop/cop/style/command_literal.rb +1 -1
  50. data/lib/rubocop/cop/style/comparable_between.rb +3 -0
  51. data/lib/rubocop/cop/style/conditional_assignment.rb +3 -1
  52. data/lib/rubocop/cop/style/data_inheritance.rb +7 -0
  53. data/lib/rubocop/cop/style/def_with_parentheses.rb +18 -5
  54. data/lib/rubocop/cop/style/empty_string_inside_interpolation.rb +100 -0
  55. data/lib/rubocop/cop/style/exponential_notation.rb +2 -2
  56. data/lib/rubocop/cop/style/fetch_env_var.rb +32 -6
  57. data/lib/rubocop/cop/style/hash_conversion.rb +12 -3
  58. data/lib/rubocop/cop/style/if_unless_modifier.rb +33 -6
  59. data/lib/rubocop/cop/style/if_unless_modifier_of_if_unless.rb +4 -7
  60. data/lib/rubocop/cop/style/it_block_parameter.rb +33 -14
  61. data/lib/rubocop/cop/style/map_to_hash.rb +11 -0
  62. data/lib/rubocop/cop/style/method_call_with_args_parentheses/omit_parentheses.rb +2 -2
  63. data/lib/rubocop/cop/style/min_max_comparison.rb +13 -5
  64. data/lib/rubocop/cop/style/multiline_if_modifier.rb +2 -0
  65. data/lib/rubocop/cop/style/percent_q_literals.rb +1 -1
  66. data/lib/rubocop/cop/style/redundant_array_flatten.rb +50 -0
  67. data/lib/rubocop/cop/style/redundant_format.rb +6 -1
  68. data/lib/rubocop/cop/style/redundant_interpolation.rb +1 -1
  69. data/lib/rubocop/cop/style/redundant_parentheses.rb +26 -5
  70. data/lib/rubocop/cop/style/redundant_self.rb +8 -5
  71. data/lib/rubocop/cop/style/regexp_literal.rb +1 -1
  72. data/lib/rubocop/cop/style/safe_navigation.rb +24 -11
  73. data/lib/rubocop/cop/style/sole_nested_conditional.rb +6 -3
  74. data/lib/rubocop/cop/style/string_concatenation.rb +1 -2
  75. data/lib/rubocop/cop/style/struct_inheritance.rb +8 -1
  76. data/lib/rubocop/cop/style/symbol_proc.rb +1 -1
  77. data/lib/rubocop/cop/style/trailing_comma_in_block_args.rb +1 -1
  78. data/lib/rubocop/cop/team.rb +1 -1
  79. data/lib/rubocop/cop/variable_force/assignment.rb +7 -3
  80. data/lib/rubocop/formatter/disabled_config_formatter.rb +1 -0
  81. data/lib/rubocop/formatter/fuubar_style_formatter.rb +1 -1
  82. data/lib/rubocop/formatter/html_formatter.rb +1 -1
  83. data/lib/rubocop/formatter/offense_count_formatter.rb +1 -1
  84. data/lib/rubocop/lsp/diagnostic.rb +4 -4
  85. data/lib/rubocop/rspec/expect_offense.rb +9 -3
  86. data/lib/rubocop/version.rb +1 -1
  87. data/lib/rubocop.rb +8 -1
  88. data/lib/ruby_lsp/rubocop/addon.rb +2 -2
  89. metadata +15 -11
@@ -0,0 +1,281 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Naming
6
+ # Checks that predicate methods end with `?` and non-predicate methods do not.
7
+ #
8
+ # The names of predicate methods (methods that return a boolean value) should end
9
+ # in a question mark. Methods that don't return a boolean, shouldn't
10
+ # end in a question mark.
11
+ #
12
+ # The cop assesses a predicate method as one that returns boolean values. Likewise,
13
+ # a method that only returns literal values is assessed as non-predicate. Other predicate
14
+ # method calls are assumed to return boolean values. The cop does not make an assessment
15
+ # if the return type is unknown (non-predicate method calls, variables, etc.).
16
+ #
17
+ # NOTE: Operator methods (`def ==`, etc.) are ignored.
18
+ #
19
+ # By default, the cop runs in `conservative` mode, which allows a method to be named
20
+ # with a question mark as long as at least one return value is boolean. In `aggressive`
21
+ # mode, methods with a question mark will register an offense if any known non-boolean
22
+ # return values are detected.
23
+ #
24
+ # The cop also has `AllowedMethods` configuration in order to prevent the cop from
25
+ # registering an offense from a method name that does not confirm to the naming
26
+ # guidelines. By default, `call` is allowed. The cop also has `AllowedPatterns`
27
+ # configuration to allow method names by regular expression.
28
+ #
29
+ # The cop can furthermore be configured to allow all bang methods (method names
30
+ # ending with `!`), with `AllowBangMethods: true` (default false).
31
+ #
32
+ # @example Mode: conservative (default)
33
+ # # bad
34
+ # def foo
35
+ # bar == baz
36
+ # end
37
+ #
38
+ # # good
39
+ # def foo?
40
+ # bar == baz
41
+ # end
42
+ #
43
+ # # bad
44
+ # def foo?
45
+ # 5
46
+ # end
47
+ #
48
+ # # good
49
+ # def foo
50
+ # 5
51
+ # end
52
+ #
53
+ # # bad
54
+ # def foo
55
+ # x == y
56
+ # end
57
+ #
58
+ # # good
59
+ # def foo?
60
+ # x == y
61
+ # end
62
+ #
63
+ # # bad
64
+ # def foo
65
+ # !x
66
+ # end
67
+ #
68
+ # # good
69
+ # def foo?
70
+ # !x
71
+ # end
72
+ #
73
+ # # bad - returns the value of another predicate method
74
+ # def foo
75
+ # bar?
76
+ # end
77
+ #
78
+ # # good
79
+ # def foo?
80
+ # bar?
81
+ # end
82
+ #
83
+ # # good - operator method
84
+ # def ==(other)
85
+ # hash == other.hash
86
+ # end
87
+ #
88
+ # # good - at least one return value is boolean
89
+ # def foo?
90
+ # return unless bar?
91
+ # true
92
+ # end
93
+ #
94
+ # # ok - return type is not known
95
+ # def foo?
96
+ # bar
97
+ # end
98
+ #
99
+ # # ok - return type is not known
100
+ # def foo
101
+ # bar?
102
+ # end
103
+ #
104
+ # @example Mode: aggressive
105
+ # # bad - the method returns nil in some cases
106
+ # def foo?
107
+ # return unless bar?
108
+ # true
109
+ # end
110
+ #
111
+ # @example AllowBangMethods: false (default)
112
+ # # bad
113
+ # def save!
114
+ # true
115
+ # end
116
+ #
117
+ # @example AllowBangMethods: true
118
+ # # good
119
+ # def save!
120
+ # true
121
+ # end
122
+ #
123
+ class PredicateMethod < Base
124
+ include AllowedMethods
125
+ include AllowedPattern
126
+
127
+ MSG_PREDICATE = 'Predicate method names should end with `?`.'
128
+ MSG_NON_PREDICATE = 'Non-predicate method names should not end with `?`.'
129
+
130
+ def on_def(node)
131
+ return if allowed?(node)
132
+
133
+ return_values = return_values(node.body)
134
+ return if acceptable?(return_values)
135
+
136
+ if node.predicate_method? && potential_non_predicate?(return_values)
137
+ add_offense(node.loc.name, message: MSG_NON_PREDICATE)
138
+ elsif !node.predicate_method? && all_return_values_boolean?(return_values)
139
+ add_offense(node.loc.name, message: MSG_PREDICATE)
140
+ end
141
+ end
142
+ alias on_defs on_def
143
+
144
+ private
145
+
146
+ def allowed?(node)
147
+ allowed_method?(node.method_name) ||
148
+ matches_allowed_pattern?(node.method_name) ||
149
+ allowed_bang_method?(node) ||
150
+ node.operator_method? ||
151
+ node.body.nil?
152
+ end
153
+
154
+ def acceptable?(return_values)
155
+ # In `conservative` mode, if the method returns `super`, `zsuper`, or a
156
+ # non-comparison method call, the method name is acceptable.
157
+ return false unless conservative?
158
+
159
+ return_values.any? do |value|
160
+ value.type?(:super, :zsuper) || unknown_method_call?(value)
161
+ end
162
+ end
163
+
164
+ def unknown_method_call?(value)
165
+ return false unless value.call_type?
166
+
167
+ !value.comparison_method? && !value.predicate_method? && !value.negation_method?
168
+ end
169
+
170
+ def return_values(node)
171
+ # Collect all the (implicit and explicit) return values of a node
172
+ return_values = Set.new(node.begin_type? ? [] : [extract_return_value(node)])
173
+
174
+ node.each_descendant(:return) do |return_node|
175
+ return_values << extract_return_value(return_node)
176
+ end
177
+
178
+ last_value = last_value(node)
179
+ return_values << last_value if last_value
180
+
181
+ process_return_values(return_values)
182
+ end
183
+
184
+ def all_return_values_boolean?(return_values)
185
+ values = return_values.reject { |value| value.type?(:super, :zsuper) }
186
+ return false if values.empty?
187
+
188
+ values.all? { |value| boolean_return?(value) }
189
+ end
190
+
191
+ def boolean_return?(value)
192
+ return true if value.boolean_type?
193
+ return false unless value.call_type?
194
+
195
+ value.comparison_method? || value.predicate_method? || value.negation_method?
196
+ end
197
+
198
+ def potential_non_predicate?(return_values)
199
+ # Assumes a method to be non-predicate if all return values are non-boolean literals.
200
+ #
201
+ # In `Mode: conservative`, if any of the return values is a boolean,
202
+ # the method name is acceptable.
203
+ # In `Mode: aggressive`, all return values must be booleans for a predicate
204
+ # method, or else an offense will be registered.
205
+ return false if conservative? && return_values.any? { |value| boolean_return?(value) }
206
+
207
+ return_values.any? do |value|
208
+ value.literal? && !value.boolean_type?
209
+ end
210
+ end
211
+
212
+ def extract_return_value(node)
213
+ return node unless node.return_type?
214
+
215
+ # `return` without a value is a `nil` return.
216
+ return s(:nil) if node.arguments.empty?
217
+
218
+ # When there's a multiple return, it cannot be a predicate
219
+ # so just return an `array` sexp for simplicity.
220
+ return s(:array) unless node.arguments.one?
221
+
222
+ node.first_argument
223
+ end
224
+
225
+ def last_value(node)
226
+ value = node.begin_type? ? node.children.last : node
227
+ value&.return_type? ? extract_return_value(value) : value
228
+ end
229
+
230
+ def process_return_values(return_values)
231
+ return_values.flat_map do |value|
232
+ if value.conditional?
233
+ process_return_values(extract_conditional_branches(value))
234
+ elsif and_or?(value)
235
+ process_return_values(extract_and_or_clauses(value))
236
+ else
237
+ value
238
+ end
239
+ end
240
+ end
241
+
242
+ def and_or?(node)
243
+ node.type?(:and, :or)
244
+ end
245
+
246
+ def extract_and_or_clauses(node)
247
+ # Recursively traverse an `and` or `or` node to collect all clauses within
248
+ return node unless and_or?(node)
249
+
250
+ [extract_and_or_clauses(node.lhs), extract_and_or_clauses(node.rhs)].flatten
251
+ end
252
+
253
+ def extract_conditional_branches(node)
254
+ return node unless node.conditional?
255
+
256
+ if node.type?(:while, :until)
257
+ # If there is no body, act as implicit `nil`.
258
+ node.body ? [last_value(node.body)] : [s(:nil)]
259
+ else
260
+ # Branches with no value act as an implicit `nil`.
261
+ node.branches.filter_map { |branch| branch ? last_value(branch) : s(:nil) }
262
+ end
263
+ end
264
+
265
+ def conservative?
266
+ cop_config.fetch('Mode', :conservative).to_sym == :conservative
267
+ end
268
+
269
+ def allowed_bang_method?(node)
270
+ return false unless allow_bang_methods?
271
+
272
+ node.bang_method?
273
+ end
274
+
275
+ def allow_bang_methods?
276
+ cop_config.fetch('AllowBangMethods', false)
277
+ end
278
+ end
279
+ end
280
+ end
281
+ end
@@ -100,12 +100,12 @@ module RuboCop
100
100
  # # good
101
101
  # def_node_matcher(:even?) { |value| }
102
102
  #
103
- class PredicateName < Base
103
+ class PredicatePrefix < Base
104
104
  include AllowedMethods
105
105
 
106
106
  # @!method dynamic_method_define(node)
107
107
  def_node_matcher :dynamic_method_define, <<~PATTERN
108
- (send nil? #method_definition_macros
108
+ (send nil? #method_definition_macro?
109
109
  (sym $_)
110
110
  ...)
111
111
  PATTERN
@@ -143,7 +143,7 @@ module RuboCop
143
143
  next if predicate_prefixes.include?(forbidden_prefix)
144
144
 
145
145
  raise ValidationError, <<~MSG.chomp
146
- The `Naming/PredicateName` cop is misconfigured. Prefix #{forbidden_prefix} must be included in NamePrefix because it is included in ForbiddenPrefixes.
146
+ The `Naming/PredicatePrefix` cop is misconfigured. Prefix #{forbidden_prefix} must be included in NamePrefix because it is included in ForbiddenPrefixes.
147
147
  MSG
148
148
  end
149
149
  end
@@ -195,7 +195,7 @@ module RuboCop
195
195
  cop_config['UseSorbetSigs']
196
196
  end
197
197
 
198
- def method_definition_macros(macro_name)
198
+ def method_definition_macro?(macro_name)
199
199
  cop_config['MethodDefinitionMacros'].include?(macro_name.to_s)
200
200
  end
201
201
  end
@@ -195,15 +195,27 @@ module RuboCop
195
195
  def autocorrect(corrector, node)
196
196
  case style
197
197
  when :group
198
- def_nodes = find_corresponding_def_nodes(node)
199
- return unless def_nodes.any?
200
-
201
- replace_defs(corrector, node, def_nodes)
198
+ autocorrect_group_style(corrector, node)
202
199
  when :inline
200
+ autocorrect_inline_style(corrector, node)
201
+ end
202
+ end
203
+
204
+ def autocorrect_group_style(corrector, node)
205
+ def_nodes = find_corresponding_def_nodes(node)
206
+ return unless def_nodes.any?
207
+
208
+ replace_defs(corrector, node, def_nodes)
209
+ end
210
+
211
+ def autocorrect_inline_style(corrector, node)
212
+ if node.parent&.begin_type?
213
+ remove_modifier_node_within_begin(corrector, node, node.parent)
214
+ else
203
215
  remove_nodes(corrector, node)
204
- select_grouped_def_nodes(node).each do |grouped_def_node|
205
- insert_inline_modifier(corrector, grouped_def_node, node.method_name)
206
- end
216
+ end
217
+ select_grouped_def_nodes(node).each do |grouped_def_node|
218
+ insert_inline_modifier(corrector, grouped_def_node, node.method_name)
207
219
  end
208
220
  end
209
221
 
@@ -224,9 +236,13 @@ module RuboCop
224
236
  end
225
237
 
226
238
  def offense?(node)
227
- (group_style? && access_modifier_is_inlined?(node) &&
228
- !node.parent&.if_type? && !right_siblings_same_inline_method?(node)) ||
229
- (inline_style? && access_modifier_is_not_inlined?(node))
239
+ if group_style?
240
+ return false if node.parent ? node.parent.if_type? : access_modifier_with_symbol?(node)
241
+
242
+ access_modifier_is_inlined?(node) && !right_siblings_same_inline_method?(node)
243
+ else
244
+ access_modifier_is_not_inlined?(node) && select_grouped_def_nodes(node).any?
245
+ end
230
246
  end
231
247
 
232
248
  def correctable_group_offense?(node)
@@ -331,6 +347,12 @@ module RuboCop
331
347
  end
332
348
  end
333
349
 
350
+ def remove_modifier_node_within_begin(corrector, modifier_node, begin_node)
351
+ def_node = begin_node.children[1]
352
+ range = modifier_node.source_range.begin.join(def_node.source_range.begin)
353
+ corrector.remove(range)
354
+ end
355
+
334
356
  def def_source(node, def_nodes)
335
357
  [
336
358
  *processed_source.ast_with_comments[node].map(&:text),
@@ -269,7 +269,7 @@ module RuboCop
269
269
  end
270
270
 
271
271
  def regexp_with_named_captures?(node)
272
- node.regexp_type? && node.each_capture(named: true).count.positive?
272
+ node.regexp_type? && node.each_capture(named: true).any?
273
273
  end
274
274
  end
275
275
  end
@@ -0,0 +1,167 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Style
6
+ # Prefer `Enumerable` predicate methods over expressions with `count`.
7
+ #
8
+ # The cop checks calls to `count` without arguments, or with a
9
+ # block. It doesn't register offenses for `count` with a positional
10
+ # argument because its behavior differs from predicate methods (`count`
11
+ # matches the argument using `==`, while `any?`, `none?` and `one?` use
12
+ # `===`).
13
+ #
14
+ # NOTE: This cop doesn't check `length` and `size` methods because they
15
+ # would yield false positives. For example, `String` implements `length`
16
+ # and `size`, but it doesn't include `Enumerable`.
17
+ #
18
+ # @safety
19
+ # The cop is unsafe because receiver might not include `Enumerable`, or
20
+ # it has nonstandard implementation of `count` or any replacement
21
+ # methods.
22
+ #
23
+ # It's also unsafe because for collections with falsey values, expressions
24
+ # with `count` without a block return a different result than methods `any?`,
25
+ # `none?` and `one?`:
26
+ #
27
+ # [source,ruby]
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
+ # ----
43
+ #
44
+ # Autocorrection is unsafe when replacement methods don't iterate over
45
+ # every element in collection and the given block runs side effects:
46
+ #
47
+ # [source,ruby]
48
+ # ----
49
+ # x.count(&:method_with_side_effects).positive?
50
+ # # calls `method_with_side_effects` on every element
51
+ #
52
+ # x.any?(&:method_with_side_effects)
53
+ # # calls `method_with_side_effects` until first element returns a truthy value
54
+ # ----
55
+ #
56
+ # @example
57
+ #
58
+ # # bad
59
+ # x.count.positive?
60
+ # x.count > 0
61
+ # x.count != 0
62
+ #
63
+ # x.count(&:foo?).positive?
64
+ # x.count { |item| item.foo? }.positive?
65
+ #
66
+ # # good
67
+ # x.any?
68
+ #
69
+ # x.any?(&:foo?)
70
+ # x.any? { |item| item.foo? }
71
+ #
72
+ # # bad
73
+ # x.count.zero?
74
+ # x.count == 0
75
+ #
76
+ # # good
77
+ # x.none?
78
+ #
79
+ # # bad
80
+ # x.count == 1
81
+ # x.one?
82
+ #
83
+ # @example AllCops:ActiveSupportExtensionsEnabled: false (default)
84
+ #
85
+ # # good
86
+ # x.count > 1
87
+ #
88
+ # @example AllCops:ActiveSupportExtensionsEnabled: true
89
+ #
90
+ # # bad
91
+ # x.count > 1
92
+ #
93
+ # # good
94
+ # x.many?
95
+ #
96
+ class CollectionQuerying < Base
97
+ include RangeHelp
98
+ extend AutoCorrector
99
+
100
+ MSG = 'Use `%<prefer>s` instead.'
101
+
102
+ RESTRICT_ON_SEND = %i[positive? > != zero? ==].freeze
103
+
104
+ REPLACEMENTS = {
105
+ [:positive?, nil] => :any?,
106
+ [:>, 0] => :any?,
107
+ [:!=, 0] => :any?,
108
+ [:zero?, nil] => :none?,
109
+ [:==, 0] => :none?,
110
+ [:==, 1] => :one?,
111
+ [:>, 1] => :many?
112
+ }.freeze
113
+
114
+ # @!method count_predicate(node)
115
+ def_node_matcher :count_predicate, <<~PATTERN
116
+ (send
117
+ {
118
+ (any_block $(call !nil? :count) _ _)
119
+ $(call !nil? :count (block-pass _)?)
120
+ }
121
+ {
122
+ :positive? |
123
+ :> (int 0) |
124
+ :!= (int 0) |
125
+ :zero? |
126
+ :== (int 0) |
127
+ :== (int 1) |
128
+ :> (int 1)
129
+ })
130
+ PATTERN
131
+
132
+ def on_send(node)
133
+ return unless (count_node = count_predicate(node))
134
+
135
+ replacement_method = replacement_method(node)
136
+
137
+ return unless replacement_supported?(replacement_method)
138
+
139
+ offense_range = count_node.loc.selector.join(node.source_range.end)
140
+ add_offense(offense_range,
141
+ message: format(MSG, prefer: replacement_method)) do |corrector|
142
+ corrector.replace(count_node.loc.selector, replacement_method)
143
+ corrector.remove(removal_range(node))
144
+ end
145
+ end
146
+
147
+ private
148
+
149
+ def replacement_method(node)
150
+ REPLACEMENTS.fetch([node.method_name, node.first_argument&.value])
151
+ end
152
+
153
+ def replacement_supported?(method_name)
154
+ return true if active_support_extensions_enabled?
155
+
156
+ method_name != :many?
157
+ end
158
+
159
+ def removal_range(node)
160
+ range = (node.loc.dot || node.loc.selector).join(node.source_range.end)
161
+
162
+ range_with_surrounding_space(range, side: :left)
163
+ end
164
+ end
165
+ end
166
+ end
167
+ end
@@ -173,7 +173,7 @@ module RuboCop
173
173
  end
174
174
 
175
175
  def preferred_delimiters_config
176
- config.for_cop('Style/PercentLiteralDelimiters') ['PreferredDelimiters']
176
+ config.for_cop('Style/PercentLiteralDelimiters')['PreferredDelimiters']
177
177
  end
178
178
  end
179
179
  end
@@ -9,6 +9,9 @@ module RuboCop
9
9
  # although the difference generally isn't observable. If you require maximum
10
10
  # performance, consider using logical comparison.
11
11
  #
12
+ # @safety
13
+ # This cop is unsafe because the receiver may not respond to `between?`.
14
+ #
12
15
  # @example
13
16
  #
14
17
  # # bad
@@ -451,7 +451,9 @@ module RuboCop
451
451
  corrector.remove_preceding(condition.loc.else, condition.loc.else.column - column)
452
452
  end
453
453
 
454
- return unless condition.loc.end && !same_line?(condition.loc.end, condition)
454
+ return unless condition.loc.end && !same_line?(
455
+ condition.branches.last.parent.else_branch, condition.loc.end
456
+ )
455
457
 
456
458
  corrector.remove_preceding(condition.loc.end, condition.loc.end.column - column)
457
459
  end
@@ -4,6 +4,7 @@ module RuboCop
4
4
  module Cop
5
5
  module Style
6
6
  # Checks for inheritance from `Data.define` to avoid creating the anonymous parent class.
7
+ # Inheriting from `Data.define` adds a superfluous level in inheritance tree.
7
8
  #
8
9
  # @safety
9
10
  # Autocorrection is unsafe because it will change the inheritance
@@ -17,12 +18,18 @@ module RuboCop
17
18
  # end
18
19
  # end
19
20
  #
21
+ # Person.ancestors
22
+ # # => [Person, #<Class:0x000000010b4e14a0>, Data, (...)]
23
+ #
20
24
  # # good
21
25
  # Person = Data.define(:first_name, :last_name) do
22
26
  # def age
23
27
  # 42
24
28
  # end
25
29
  # end
30
+ #
31
+ # Person.ancestors
32
+ # # => [Person, Data, (...)]
26
33
  class DataInheritance < Base
27
34
  include RangeHelp
28
35
  extend AutoCorrector
@@ -25,8 +25,9 @@ module RuboCop
25
25
  # # good
26
26
  # def foo = do_something
27
27
  #
28
- # # good (without parentheses it's a syntax error)
28
+ # # good - without parentheses it's a syntax error
29
29
  # def foo() do_something end
30
+ # def foo()=do_something
30
31
  #
31
32
  # # bad
32
33
  # def Baz.foo()
@@ -38,19 +39,31 @@ module RuboCop
38
39
  # do_something
39
40
  # end
40
41
  class DefWithParentheses < Base
42
+ include RangeHelp
41
43
  extend AutoCorrector
42
44
 
43
45
  MSG = "Omit the parentheses in defs when the method doesn't accept any arguments."
44
46
 
45
47
  def on_def(node)
46
- return if node.single_line? && !node.endless?
47
- return unless !node.arguments? && (node_arguments = node.arguments.source_range)
48
+ return unless !node.arguments? && (arguments_range = node.arguments.source_range)
49
+ return if parentheses_required?(node, arguments_range)
48
50
 
49
- add_offense(node_arguments) do |corrector|
50
- corrector.remove(node_arguments)
51
+ add_offense(arguments_range) do |corrector|
52
+ corrector.remove(arguments_range)
51
53
  end
52
54
  end
53
55
  alias on_defs on_def
56
+
57
+ private
58
+
59
+ def parentheses_required?(node, arguments_range)
60
+ return true if node.single_line? && !node.endless?
61
+
62
+ end_pos = arguments_range.end.end_pos
63
+ token_after_argument = range_between(end_pos, end_pos + 1).source
64
+
65
+ token_after_argument == '='
66
+ end
54
67
  end
55
68
  end
56
69
  end