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
@@ -22,6 +22,11 @@ module RuboCop
22
22
  # * Private attribute macros (`attr_accessor`, `attr_writer`, `attr_reader`)
23
23
  # * Private instance methods
24
24
  #
25
+ # NOTE: Simply enabling the cop with `Enabled: true` will not use
26
+ # the example order shown below.
27
+ # To enforce the order of macros like `attr_reader`,
28
+ # you must define both `ExpectedOrder` *and* `Categories`.
29
+ #
25
30
  # You can configure the following order:
26
31
  #
27
32
  # [source,yaml]
@@ -68,6 +73,36 @@ module RuboCop
68
73
  # - extend
69
74
  # ----
70
75
  #
76
+ # If you only set `ExpectedOrder`
77
+ # without defining `Categories`,
78
+ # macros such as `attr_reader` or `has_many`
79
+ # will not be recognized as part of a category, and their order will not be validated.
80
+ # For example, the following will NOT raise any offenses, even if the order is incorrect:
81
+ #
82
+ # [source,yaml]
83
+ # ----
84
+ # Layout/ClassStructure:
85
+ # Enabled: true
86
+ # ExpectedOrder:
87
+ # - public_attribute_macros
88
+ # - initializer
89
+ # ----
90
+ #
91
+ # To make it work as expected, you must also specify `Categories` like this:
92
+ #
93
+ # [source,yaml]
94
+ # ----
95
+ # Layout/ClassStructure:
96
+ # ExpectedOrder:
97
+ # - public_attribute_macros
98
+ # - initializer
99
+ # Categories:
100
+ # attribute_macros:
101
+ # - attr_reader
102
+ # - attr_writer
103
+ # - attr_accessor
104
+ # ----
105
+ #
71
106
  # @safety
72
107
  # Autocorrection is unsafe because class methods and module inclusion
73
108
  # can behave differently, based on which methods or constants have
@@ -161,7 +161,7 @@ module RuboCop
161
161
  elements.flat_map do |e|
162
162
  e.loc.column
163
163
  end
164
- end.uniq.count == 1
164
+ end.uniq.one?
165
165
  end
166
166
 
167
167
  def first_argument_line(elements)
@@ -116,7 +116,7 @@ module RuboCop
116
116
  def allowed_only_before_style?(node)
117
117
  if node.special_modifier?
118
118
  return true if processed_source[node.last_line] == 'end'
119
- return false if next_line_empty?(node.last_line)
119
+ return false if next_line_empty_and_exists?(node.last_line)
120
120
  end
121
121
 
122
122
  previous_line_empty?(node.first_line)
@@ -129,7 +129,7 @@ module RuboCop
129
129
  when :around
130
130
  corrector.insert_after(line, "\n") unless next_line_empty?(node.last_line)
131
131
  when :only_before
132
- if next_line_empty?(node.last_line)
132
+ if next_line_empty_and_exists?(node.last_line)
133
133
  range = next_empty_line_range(node)
134
134
 
135
135
  corrector.remove(range)
@@ -138,7 +138,7 @@ module RuboCop
138
138
  end
139
139
 
140
140
  def previous_line_ignoring_comments(processed_source, send_line)
141
- processed_source[0..send_line - 2].reverse.find { |line| !comment_line?(line) }
141
+ processed_source[0..(send_line - 2)].reverse.find { |line| !comment_line?(line) }
142
142
  end
143
143
 
144
144
  def previous_line_empty?(send_line)
@@ -154,6 +154,10 @@ module RuboCop
154
154
  body_end?(last_send_line) || next_line.blank?
155
155
  end
156
156
 
157
+ def next_line_empty_and_exists?(last_send_line)
158
+ next_line_empty?(last_send_line) && last_send_line.next != processed_source.lines.size
159
+ end
160
+
157
161
  def empty_lines_around?(node)
158
162
  previous_line_empty?(node.first_line) && next_line_empty?(node.last_line)
159
163
  end
@@ -155,7 +155,7 @@ module RuboCop
155
155
  def on_send(node)
156
156
  return unless should_check?(node)
157
157
  return if same_line?(node, node.first_argument)
158
- return if style != :consistent && enforce_first_argument_with_fixed_indentation? &&
158
+ return if enforce_first_argument_with_fixed_indentation? &&
159
159
  !enable_layout_first_method_argument_line_break?
160
160
 
161
161
  indent = base_indentation(node) + configured_indentation_width
@@ -258,7 +258,7 @@ module RuboCop
258
258
  if ignore_cop_directives? && directive_on_source_line?(line_index)
259
259
  return check_directive_line(line, line_index)
260
260
  end
261
- return check_uri_line(line, line_index) if allow_uri?
261
+ return check_line_for_exemptions(line, line_index) if allow_uri? || allow_qualified_name?
262
262
 
263
263
  register_offense(excess_range(nil, line, line_index), line, line_index)
264
264
  end
@@ -358,11 +358,32 @@ module RuboCop
358
358
  )
359
359
  end
360
360
 
361
- def check_uri_line(line, line_index)
362
- uri_range = find_excessive_uri_range(line)
363
- return if uri_range && allowed_uri_position?(line, uri_range)
361
+ def check_line_for_exemptions(line, line_index)
362
+ uri_range = range_if_applicable(line, :uri)
363
+ qualified_name_range = range_if_applicable(line, :qualified_name)
364
364
 
365
- register_offense(excess_range(uri_range, line, line_index), line, line_index)
365
+ return if allowed_combination?(line, uri_range, qualified_name_range)
366
+
367
+ range = uri_range || qualified_name_range
368
+ register_offense(excess_range(range, line, line_index), line, line_index)
369
+ end
370
+
371
+ def range_if_applicable(line, type)
372
+ return unless type == :uri ? allow_uri? : allow_qualified_name?
373
+
374
+ find_excessive_range(line, type)
375
+ end
376
+
377
+ def allowed_combination?(line, uri_range, qualified_name_range)
378
+ if uri_range && qualified_name_range
379
+ allowed_position?(line, uri_range) && allowed_position?(line, qualified_name_range)
380
+ elsif uri_range
381
+ allowed_position?(line, uri_range)
382
+ elsif qualified_name_range
383
+ allowed_position?(line, qualified_name_range)
384
+ else
385
+ false
386
+ end
366
387
  end
367
388
 
368
389
  def breakable_dstr?(node)
@@ -22,50 +22,17 @@ module RuboCop
22
22
  RESTRICT_ON_SEND = %i[[] []=].freeze
23
23
 
24
24
  def on_send(node)
25
- return unless (first_argument = node.first_argument)
25
+ return if node.loc.dot
26
26
 
27
- begin_pos = first_argument.source_range.begin_pos
28
- return unless (range = offense_range(node, begin_pos))
29
-
30
- register_offense(range)
31
- end
32
-
33
- private
34
-
35
- def offense_range(node, begin_pos)
36
27
  receiver_end_pos = node.receiver.source_range.end_pos
37
28
  selector_begin_pos = node.loc.selector.begin_pos
38
29
  return if receiver_end_pos >= selector_begin_pos
39
- return if dot_before_brackets?(node, receiver_end_pos, selector_begin_pos)
40
-
41
- if reference_variable_with_brackets?(node)
42
- range_between(receiver_end_pos, selector_begin_pos)
43
- elsif node.method?(:[]=)
44
- offense_range_for_assignment(node, begin_pos)
45
- end
46
- end
47
-
48
- def dot_before_brackets?(node, receiver_end_pos, selector_begin_pos)
49
- return false unless node.loc.respond_to?(:dot) && (dot = node.loc.dot)
50
30
 
51
- dot.begin_pos == receiver_end_pos && dot.end_pos == selector_begin_pos
52
- end
53
-
54
- def offense_range_for_assignment(node, begin_pos)
55
- end_pos = node.receiver.source_range.end_pos
56
-
57
- return if begin_pos - end_pos == 1 ||
58
- (range = range_between(end_pos, begin_pos - 1)).source.start_with?('[')
59
-
60
- range
61
- end
62
-
63
- def register_offense(range)
64
- add_offense(range) { |corrector| corrector.remove(range) }
65
- end
31
+ range = range_between(receiver_end_pos, selector_begin_pos)
66
32
 
67
- def reference_variable_with_brackets?(node)
68
- node.receiver&.variable? && node.method?(:[]) && node.arguments.size == 1
33
+ add_offense(range) do |corrector|
34
+ corrector.remove(range)
35
+ end
69
36
  end
70
37
  end
71
38
  end
@@ -86,7 +86,9 @@ module RuboCop
86
86
  def on_array(node)
87
87
  return if node.array_type? && !node.square_brackets?
88
88
 
89
+ node = find_node_with_brackets(node)
89
90
  tokens, left, right = array_brackets(node)
91
+ return unless left && right
90
92
 
91
93
  if empty_brackets?(left, right, tokens: tokens)
92
94
  return empty_offenses(node, left, right, EMPTY_MSG)
@@ -101,6 +103,10 @@ module RuboCop
101
103
 
102
104
  private
103
105
 
106
+ def find_node_with_brackets(node)
107
+ node.ancestors.find(&:const_pattern_type?) || node
108
+ end
109
+
104
110
  def autocorrect(corrector, node)
105
111
  tokens, left, right = array_brackets(node)
106
112
 
@@ -118,7 +124,7 @@ module RuboCop
118
124
  def array_brackets(node)
119
125
  tokens = processed_source.tokens_within(node)
120
126
 
121
- left = tokens.find(&:left_array_bracket?)
127
+ left = tokens.find(&:left_bracket?)
122
128
  right = tokens.reverse_each.find(&:right_bracket?)
123
129
 
124
130
  [tokens, left, right]
@@ -191,7 +197,7 @@ module RuboCop
191
197
  if side == :right
192
198
  processed_source.tokens_within(node)[i].right_bracket?
193
199
  else
194
- processed_source.tokens_within(node)[i].left_array_bracket?
200
+ processed_source.tokens_within(node)[i].left_bracket?
195
201
  end
196
202
  end
197
203
 
@@ -27,7 +27,9 @@ module RuboCop
27
27
  # @example
28
28
  # # bad
29
29
  # x || 1..2
30
+ # x - 1..2
30
31
  # (x || 1..2)
32
+ # x || 1..y || 2
31
33
  # 1..2.to_a
32
34
  #
33
35
  # # good, unambiguous
@@ -41,6 +43,7 @@ module RuboCop
41
43
  #
42
44
  # # good, ambiguity removed
43
45
  # x || (1..2)
46
+ # (x - 1)..2
44
47
  # (x || 1)..2
45
48
  # (x || 1)..(y || 2)
46
49
  # (1..2).to_a
@@ -96,6 +99,8 @@ module RuboCop
96
99
  # to avoid the ambiguity of `1..2.to_a`.
97
100
  return false if node.receiver&.basic_literal?
98
101
 
102
+ return false if node.operator_method? && !node.method?(:[])
103
+
99
104
  require_parentheses_for_method_chain? || node.receiver.nil?
100
105
  end
101
106
 
@@ -43,7 +43,7 @@ module RuboCop
43
43
  dup: 'to_h',
44
44
  exists?: 'exist?',
45
45
  gethostbyaddr: 'Addrinfo#getnameinfo',
46
- gethostbyname: 'Addrinfo#getaddrinfo',
46
+ gethostbyname: 'Addrinfo.getaddrinfo',
47
47
  iterator?: 'block_given?'
48
48
  }.freeze
49
49
 
@@ -39,9 +39,52 @@ module RuboCop
39
39
  # end
40
40
  #
41
41
  # alias bar foo
42
+ #
43
+ # @example AllCops:ActiveSupportExtensionsEnabled: false (default)
44
+ #
45
+ # # good
46
+ # def foo
47
+ # 1
48
+ # end
49
+ #
50
+ # delegate :foo, to: :bar
51
+ #
52
+ # @example AllCops:ActiveSupportExtensionsEnabled: true
53
+ #
54
+ # # bad
55
+ # def foo
56
+ # 1
57
+ # end
58
+ #
59
+ # delegate :foo, to: :bar
60
+ #
61
+ # # good
62
+ # def foo
63
+ # 1
64
+ # end
65
+ #
66
+ # delegate :baz, to: :bar
67
+ #
68
+ # # good - delegate with splat arguments is ignored
69
+ # def foo
70
+ # 1
71
+ # end
72
+ #
73
+ # delegate :foo, **options
74
+ #
75
+ # # good - delegate inside a condition is ignored
76
+ # def foo
77
+ # 1
78
+ # end
79
+ #
80
+ # if cond
81
+ # delegate :foo, to: :bar
82
+ # end
83
+ #
42
84
  class DuplicateMethods < Base
43
85
  MSG = 'Method `%<method>s` is defined at both %<defined>s and %<current>s.'
44
- RESTRICT_ON_SEND = %i[alias_method attr_reader attr_writer attr_accessor attr].freeze
86
+ RESTRICT_ON_SEND = %i[alias_method attr_reader attr_writer attr_accessor attr
87
+ delegate].freeze
45
88
 
46
89
  def initialize(config = nil, options = nil)
47
90
  super
@@ -85,15 +128,28 @@ module RuboCop
85
128
  (send nil? :alias_method (sym $_name) _)
86
129
  PATTERN
87
130
 
131
+ # @!method delegate_method?(node)
132
+ def_node_matcher :delegate_method?, <<~PATTERN
133
+ (send nil? :delegate
134
+ ({sym str} $_)+
135
+ (hash <(pair (sym :to) {sym str}) ...>)
136
+ )
137
+ PATTERN
138
+
88
139
  # @!method sym_name(node)
89
140
  def_node_matcher :sym_name, '(sym $_name)'
90
- def on_send(node)
141
+
142
+ def on_send(node) # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
91
143
  if (name = alias_method?(node))
92
144
  return if node.ancestors.any?(&:if_type?)
93
145
 
94
146
  found_instance_method(node, name)
95
147
  elsif (attr = node.attribute_accessor?)
96
148
  on_attr(node, *attr)
149
+ elsif active_support_extensions_enabled? && (names = delegate_method?(node))
150
+ return if node.ancestors.any?(&:if_type?)
151
+
152
+ on_delegate(node, names)
97
153
  end
98
154
  end
99
155
 
@@ -118,6 +174,32 @@ module RuboCop
118
174
  current: source_location(node))
119
175
  end
120
176
 
177
+ def on_delegate(node, method_names)
178
+ name_prefix = delegate_prefix(node)
179
+
180
+ method_names.each do |name|
181
+ name = "#{name_prefix}_#{name}" if name_prefix
182
+
183
+ found_instance_method(node, name)
184
+ end
185
+ end
186
+
187
+ def delegate_prefix(node)
188
+ kwargs_node = node.last_argument
189
+
190
+ return unless (prefix = hash_value(kwargs_node, :prefix))
191
+
192
+ if prefix.true_type?
193
+ hash_value(kwargs_node, :to).value
194
+ elsif prefix.type?(:sym, :str)
195
+ prefix.value
196
+ end
197
+ end
198
+
199
+ def hash_value(node, key)
200
+ node.pairs.find { |pair| pair.key.value == key }&.value
201
+ end
202
+
121
203
  def found_instance_method(node, name)
122
204
  return found_sclass_method(node, name) unless (scope = node.parent_module_name)
123
205
 
@@ -19,7 +19,9 @@ module RuboCop
19
19
  MSG = 'Empty interpolation detected.'
20
20
 
21
21
  def on_interpolation(begin_node)
22
- return unless begin_node.children.empty?
22
+ node_children = begin_node.children.dup
23
+ node_children.delete_if { |e| e.nil_type? || (e.basic_literal? && e.str_content&.empty?) }
24
+ return unless node_children.empty?
23
25
 
24
26
  add_offense(begin_node) { |corrector| corrector.remove(begin_node) }
25
27
  end
@@ -15,6 +15,14 @@ module RuboCop
15
15
  # x == 0.1
16
16
  # x != 0.1
17
17
  #
18
+ # # bad
19
+ # case value
20
+ # when 1.0
21
+ # foo
22
+ # when 2.0
23
+ # bar
24
+ # end
25
+ #
18
26
  # # good - using BigDecimal
19
27
  # x.to_d == 0.1.to_d
20
28
  #
@@ -32,12 +40,21 @@ module RuboCop
32
40
  # # good - comparing against nil
33
41
  # Float(x, exception: false) == nil
34
42
  #
43
+ # # good - using epsilon comparison in case expression
44
+ # case
45
+ # when (value - 1.0).abs < Float::EPSILON
46
+ # foo
47
+ # when (value - 2.0).abs < Float::EPSILON
48
+ # bar
49
+ # end
50
+ #
35
51
  # # Or some other epsilon based type of comparison:
36
52
  # # https://www.embeddeduse.com/2019/08/26/qt-compare-two-floats/
37
53
  #
38
54
  class FloatComparison < Base
39
55
  MSG_EQUALITY = 'Avoid equality comparisons of floats as they are unreliable.'
40
56
  MSG_INEQUALITY = 'Avoid inequality comparisons of floats as they are unreliable.'
57
+ MSG_CASE = 'Avoid float literal comparisons in case statements as they are unreliable.'
41
58
 
42
59
  EQUALITY_METHODS = %i[== != eql? equal?].freeze
43
60
  FLOAT_RETURNING_METHODS = %i[to_f Float fdiv].freeze
@@ -58,6 +75,16 @@ module RuboCop
58
75
  end
59
76
  alias on_csend on_send
60
77
 
78
+ def on_case(node)
79
+ node.when_branches.each do |when_branch|
80
+ when_branch.each_condition do |condition|
81
+ next if !float?(condition) || literal_safe?(condition)
82
+
83
+ add_offense(condition, message: MSG_CASE)
84
+ end
85
+ end
86
+ end
87
+
61
88
  private
62
89
 
63
90
  def float?(node)
@@ -67,7 +94,7 @@ module RuboCop
67
94
  when :float
68
95
  true
69
96
  when :send
70
- check_send(node)
97
+ float_send?(node)
71
98
  when :begin
72
99
  float?(node.children.first)
73
100
  else
@@ -81,18 +108,18 @@ module RuboCop
81
108
  (node.numeric_type? && node.value.zero?) || node.nil_type?
82
109
  end
83
110
 
84
- def check_send(node)
111
+ def float_send?(node)
85
112
  if node.arithmetic_operation?
86
113
  float?(node.receiver) || float?(node.first_argument)
87
114
  elsif FLOAT_RETURNING_METHODS.include?(node.method_name)
88
115
  true
89
116
  elsif node.receiver&.float_type?
90
117
  FLOAT_INSTANCE_METHODS.include?(node.method_name) ||
91
- check_numeric_returning_method(node)
118
+ numeric_returning_method?(node)
92
119
  end
93
120
  end
94
121
 
95
- def check_numeric_returning_method(node)
122
+ def numeric_returning_method?(node)
96
123
  return false unless node.receiver
97
124
 
98
125
  case node.method_name
@@ -11,39 +11,43 @@ module RuboCop
11
11
  # @example
12
12
  # # bad
13
13
  # foo.object_id == bar.object_id
14
+ # foo.object_id != baz.object_id
14
15
  #
15
16
  # # good
16
17
  # foo.equal?(bar)
18
+ # !foo.equal?(baz)
17
19
  #
18
20
  class IdentityComparison < Base
19
21
  extend AutoCorrector
20
22
 
21
- MSG = 'Use `equal?` instead `==` when comparing `object_id`.'
22
- RESTRICT_ON_SEND = %i[==].freeze
23
+ MSG = 'Use `%<bang>sequal?` instead of `%<comparison_method>s` when comparing `object_id`.'
24
+ RESTRICT_ON_SEND = %i[== !=].freeze
25
+
26
+ # @!method object_id_comparison(node)
27
+ def_node_matcher :object_id_comparison, <<~PATTERN
28
+ (send
29
+ (send
30
+ _lhs_receiver :object_id) ${:== :!=}
31
+ (send
32
+ _rhs_receiver :object_id))
33
+ PATTERN
23
34
 
24
35
  def on_send(node)
25
- return unless compare_between_object_id_by_double_equal?(node)
36
+ return unless (comparison_method = object_id_comparison(node))
26
37
 
27
- add_offense(node) do |corrector|
38
+ bang = comparison_method == :== ? '' : '!'
39
+ add_offense(node,
40
+ message: format(MSG, comparison_method: comparison_method,
41
+ bang: bang)) do |corrector|
28
42
  receiver = node.receiver.receiver
29
43
  argument = node.first_argument.receiver
30
44
  return unless receiver && argument
31
45
 
32
- replacement = "#{receiver.source}.equal?(#{argument.source})"
46
+ replacement = "#{bang}#{receiver.source}.equal?(#{argument.source})"
33
47
 
34
48
  corrector.replace(node, replacement)
35
49
  end
36
50
  end
37
-
38
- private
39
-
40
- def compare_between_object_id_by_double_equal?(node)
41
- object_id_method?(node.receiver) && object_id_method?(node.first_argument)
42
- end
43
-
44
- def object_id_method?(node)
45
- node.send_type? && node.method?(:object_id)
46
- end
47
51
  end
48
52
  end
49
53
  end
@@ -228,7 +228,7 @@ module RuboCop
228
228
  )
229
229
  end
230
230
 
231
- def condition_evaluation(node, cond)
231
+ def condition_evaluation?(node, cond)
232
232
  if node.unless?
233
233
  cond.falsey_literal?
234
234
  else
@@ -238,32 +238,24 @@ module RuboCop
238
238
 
239
239
  # rubocop:disable Metrics/AbcSize, Metrics/MethodLength, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
240
240
  def correct_if_node(node, cond)
241
- result = condition_evaluation(node, cond)
242
-
243
- if node.elsif? && result
244
- add_offense(cond) do |corrector|
245
- corrector.replace(node, "else\n #{node.if_branch.source}")
246
- end
247
- elsif node.elsif? && !result
248
- add_offense(cond) do |corrector|
249
- corrector.replace(node, "else\n #{node.else_branch.source}")
250
- end
251
- elsif node.if_branch && result
252
- add_offense(cond) do |corrector|
253
- corrector.replace(node, node.if_branch.source)
254
- end
255
- elsif node.elsif_conditional?
256
- add_offense(cond) do |corrector|
257
- corrector.replace(node, "#{node.else_branch.source.sub('elsif', 'if')}\nend")
258
- end
259
- elsif node.else? || node.ternary?
260
- add_offense(cond) do |corrector|
261
- corrector.replace(node, node.else_branch.source)
262
- end
263
- else
264
- add_offense(cond) do |corrector|
265
- corrector.remove(node)
266
- end
241
+ result = condition_evaluation?(node, cond)
242
+
243
+ new_node = if node.elsif? && result
244
+ "else\n #{range_with_comments(node.if_branch).source}"
245
+ elsif node.elsif? && !result
246
+ "else\n #{node.else_branch.source}"
247
+ elsif node.if_branch && result
248
+ node.if_branch.source
249
+ elsif node.elsif_conditional?
250
+ "#{node.else_branch.source.sub('elsif', 'if')}\nend"
251
+ elsif node.else? || node.ternary?
252
+ node.else_branch.source
253
+ else
254
+ '' # Equivalent to removing the node
255
+ end
256
+
257
+ add_offense(cond) do |corrector|
258
+ corrector.replace(node, new_node)
267
259
  end
268
260
  end
269
261
  # rubocop:enable Metrics/AbcSize, Metrics/MethodLength, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
@@ -73,7 +73,7 @@ module RuboCop
73
73
  end
74
74
 
75
75
  def redundant_group?(expr)
76
- expr.is?(:passive, :group) && expr.count { |child| child.type != :free_space } == 1
76
+ expr.is?(:passive, :group) && expr.one? { |child| child.type != :free_space }
77
77
  end
78
78
 
79
79
  def redundantly_quantifiable?(node)
@@ -97,7 +97,7 @@ module RuboCop
97
97
  end
98
98
 
99
99
  def require_parentheses?(send_node)
100
- return true if operator_inside_hash?(send_node)
100
+ return true if operator_inside_collection_literal?(send_node)
101
101
  return false unless send_node.comparison_method?
102
102
  return false unless (node = send_node.parent)
103
103
 
@@ -105,10 +105,10 @@ module RuboCop
105
105
  (node.respond_to?(:comparison_method?) && node.comparison_method?)
106
106
  end
107
107
 
108
- def operator_inside_hash?(send_node)
109
- # If an operator call (without a dot) is inside a hash, it needs
108
+ def operator_inside_collection_literal?(send_node)
109
+ # If an operator call (without a dot) is inside an array or a hash, it needs
110
110
  # to be parenthesized when converted to safe navigation.
111
- send_node.parent&.pair_type? && !send_node.loc.dot
111
+ send_node.parent&.type?(:array, :pair) && !send_node.loc.dot
112
112
  end
113
113
  end
114
114
  end