rubocop 1.82.0 → 1.84.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 (76) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE.txt +1 -1
  3. data/README.md +1 -1
  4. data/config/default.yml +44 -0
  5. data/lib/rubocop/cli/command/lsp.rb +1 -1
  6. data/lib/rubocop/cli.rb +2 -1
  7. data/lib/rubocop/comment_config.rb +1 -0
  8. data/lib/rubocop/cop/autocorrect_logic.rb +2 -0
  9. data/lib/rubocop/cop/bundler/gem_version.rb +28 -28
  10. data/lib/rubocop/cop/bundler/ordered_gems.rb +1 -2
  11. data/lib/rubocop/cop/correctors/alignment_corrector.rb +20 -2
  12. data/lib/rubocop/cop/gemspec/ordered_dependencies.rb +1 -2
  13. data/lib/rubocop/cop/internal_affairs/example_heredoc_delimiter.rb +8 -8
  14. data/lib/rubocop/cop/internal_affairs/node_matcher_directive.rb +9 -9
  15. data/lib/rubocop/cop/internal_affairs/useless_message_assertion.rb +4 -4
  16. data/lib/rubocop/cop/layout/case_indentation.rb +3 -1
  17. data/lib/rubocop/cop/layout/class_structure.rb +12 -5
  18. data/lib/rubocop/cop/layout/first_argument_indentation.rb +32 -1
  19. data/lib/rubocop/cop/layout/first_array_element_line_break.rb +26 -0
  20. data/lib/rubocop/cop/layout/first_hash_element_line_break.rb +25 -25
  21. data/lib/rubocop/cop/layout/heredoc_indentation.rb +35 -1
  22. data/lib/rubocop/cop/layout/indentation_width.rb +111 -7
  23. data/lib/rubocop/cop/layout/line_length.rb +6 -2
  24. data/lib/rubocop/cop/layout/multiline_array_brace_layout.rb +57 -57
  25. data/lib/rubocop/cop/layout/multiline_block_layout.rb +2 -0
  26. data/lib/rubocop/cop/layout/multiline_hash_brace_layout.rb +56 -56
  27. data/lib/rubocop/cop/layout/multiline_method_call_indentation.rb +5 -1
  28. data/lib/rubocop/cop/layout/space_after_comma.rb +2 -10
  29. data/lib/rubocop/cop/layout/space_after_semicolon.rb +1 -1
  30. data/lib/rubocop/cop/layout/space_in_lambda_literal.rb +8 -8
  31. data/lib/rubocop/cop/lint/duplicate_match_pattern.rb +4 -4
  32. data/lib/rubocop/cop/lint/duplicate_methods.rb +57 -5
  33. data/lib/rubocop/cop/lint/float_comparison.rb +1 -1
  34. data/lib/rubocop/cop/lint/literal_as_condition.rb +1 -1
  35. data/lib/rubocop/cop/lint/redundant_splat_expansion.rb +1 -1
  36. data/lib/rubocop/cop/lint/struct_new_override.rb +17 -1
  37. data/lib/rubocop/cop/lint/to_json.rb +12 -16
  38. data/lib/rubocop/cop/lint/useless_assignment.rb +44 -16
  39. data/lib/rubocop/cop/lint/useless_or.rb +1 -1
  40. data/lib/rubocop/cop/lint/utils/nil_receiver_checker.rb +1 -1
  41. data/lib/rubocop/cop/mixin/check_single_line_suitability.rb +2 -0
  42. data/lib/rubocop/cop/mixin/hash_shorthand_syntax.rb +4 -4
  43. data/lib/rubocop/cop/mixin/space_after_punctuation.rb +5 -4
  44. data/lib/rubocop/cop/mixin/trailing_comma.rb +5 -1
  45. data/lib/rubocop/cop/naming/predicate_prefix.rb +11 -11
  46. data/lib/rubocop/cop/offense.rb +2 -1
  47. data/lib/rubocop/cop/security/json_load.rb +1 -1
  48. data/lib/rubocop/cop/style/access_modifier_declarations.rb +1 -2
  49. data/lib/rubocop/cop/style/documentation.rb +6 -6
  50. data/lib/rubocop/cop/style/documentation_method.rb +8 -8
  51. data/lib/rubocop/cop/style/empty_class_definition.rb +144 -0
  52. data/lib/rubocop/cop/style/guard_clause.rb +7 -4
  53. data/lib/rubocop/cop/style/hash_lookup_method.rb +94 -0
  54. data/lib/rubocop/cop/style/if_unless_modifier_of_if_unless.rb +12 -12
  55. data/lib/rubocop/cop/style/lambda_call.rb +8 -8
  56. data/lib/rubocop/cop/style/module_member_existence_check.rb +56 -13
  57. data/lib/rubocop/cop/style/multiline_method_signature.rb +2 -0
  58. data/lib/rubocop/cop/style/negative_array_index.rb +218 -0
  59. data/lib/rubocop/cop/style/operator_method_call.rb +11 -2
  60. data/lib/rubocop/cop/style/preferred_hash_methods.rb +12 -12
  61. data/lib/rubocop/cop/style/redundant_condition.rb +1 -1
  62. data/lib/rubocop/cop/style/reverse_find.rb +51 -0
  63. data/lib/rubocop/cop/team.rb +3 -3
  64. data/lib/rubocop/cop/variable_force/branch.rb +28 -4
  65. data/lib/rubocop/formatter/clang_style_formatter.rb +5 -2
  66. data/lib/rubocop/formatter/formatter_set.rb +1 -1
  67. data/lib/rubocop/formatter/tap_formatter.rb +5 -2
  68. data/lib/rubocop/remote_config.rb +5 -2
  69. data/lib/rubocop/result_cache.rb +38 -27
  70. data/lib/rubocop/rspec/shared_contexts.rb +4 -0
  71. data/lib/rubocop/rspec/support.rb +1 -0
  72. data/lib/rubocop/runner.rb +4 -0
  73. data/lib/rubocop/target_ruby.rb +3 -1
  74. data/lib/rubocop/version.rb +1 -1
  75. data/lib/rubocop.rb +4 -0
  76. metadata +11 -7
@@ -8,20 +8,20 @@ module RuboCop
8
8
  #
9
9
  # @example
10
10
  #
11
- # # bad
12
- # tired? ? 'stop' : 'go faster' if running?
11
+ # # bad
12
+ # tired? ? 'stop' : 'go faster' if running?
13
13
  #
14
- # # bad
15
- # if tired?
16
- # "please stop"
17
- # else
18
- # "keep going"
19
- # end if running?
14
+ # # bad
15
+ # if tired?
16
+ # "please stop"
17
+ # else
18
+ # "keep going"
19
+ # end if running?
20
20
  #
21
- # # good
22
- # if running?
23
- # tired? ? 'stop' : 'go faster'
24
- # end
21
+ # # good
22
+ # if running?
23
+ # tired? ? 'stop' : 'go faster'
24
+ # end
25
25
  class IfUnlessModifierOfIfUnless < Base
26
26
  include StatementModifier
27
27
  extend AutoCorrector
@@ -6,18 +6,18 @@ module RuboCop
6
6
  # Checks for use of the lambda.(args) syntax.
7
7
  #
8
8
  # @example EnforcedStyle: call (default)
9
- # # bad
10
- # lambda.(x, y)
9
+ # # bad
10
+ # lambda.(x, y)
11
11
  #
12
- # # good
13
- # lambda.call(x, y)
12
+ # # good
13
+ # lambda.call(x, y)
14
14
  #
15
15
  # @example EnforcedStyle: braces
16
- # # bad
17
- # lambda.call(x, y)
16
+ # # bad
17
+ # lambda.call(x, y)
18
18
  #
19
- # # good
20
- # lambda.(x, y)
19
+ # # good
20
+ # lambda.(x, y)
21
21
  class LambdaCall < Base
22
22
  include ConfigurableEnforcedStyle
23
23
  extend AutoCorrector
@@ -26,33 +26,65 @@ module RuboCop
26
26
  #
27
27
  # Array.method_defined?(:find, false)
28
28
  #
29
+ # # bad
30
+ # Array.class_variables.include?(:foo)
31
+ # Array.constants.include?(:foo)
32
+ # Array.private_instance_methods.include?(:foo)
33
+ # Array.protected_instance_methods.include?(:foo)
34
+ # Array.public_instance_methods.include?(:foo)
35
+ # Array.included_modules.include?(:foo)
36
+ #
37
+ # # good
38
+ # Array.class_variable_defined?(:foo)
39
+ # Array.const_defined?(:foo)
40
+ # Array.private_method_defined?(:foo)
41
+ # Array.protected_method_defined?(:foo)
42
+ # Array.public_method_defined?(:foo)
43
+ # Array.include?(:foo)
44
+ #
45
+ # @example AllowedMethods: [included_modules]
46
+ #
47
+ # # good
48
+ # Array.included_modules.include?(:foo)
49
+ #
29
50
  class ModuleMemberExistenceCheck < Base
51
+ include AllowedMethods
30
52
  extend AutoCorrector
31
53
 
32
54
  MSG = 'Use `%<replacement>s` instead.'
33
55
 
34
- RESTRICT_ON_SEND = %i[instance_methods].freeze
35
-
36
- # @!method instance_methods_inclusion?(node)
37
- def_node_matcher :instance_methods_inclusion?, <<~PATTERN
56
+ # @!method module_member_inclusion?(node)
57
+ def_node_matcher :module_member_inclusion?, <<~PATTERN
38
58
  (call
39
- (call _ :instance_methods _?)
59
+ {(call _ %METHODS_WITH_INHERIT_PARAM _?) (call _ %METHODS_WITHOUT_INHERIT_PARAM)}
40
60
  {:include? :member?}
41
61
  _)
42
62
  PATTERN
43
63
 
44
- def on_send(node) # rubocop:disable Metrics/AbcSize
64
+ METHOD_REPLACEMENTS = {
65
+ class_variables: :class_variable_defined?,
66
+ constants: :const_defined?,
67
+ included_modules: :include?,
68
+ instance_methods: :method_defined?,
69
+ private_instance_methods: :private_method_defined?,
70
+ protected_instance_methods: :protected_method_defined?,
71
+ public_instance_methods: :public_method_defined?
72
+ }.freeze
73
+
74
+ METHODS_WITHOUT_INHERIT_PARAM = Set[:class_variables, :included_modules].freeze
75
+ METHODS_WITH_INHERIT_PARAM =
76
+ (METHOD_REPLACEMENTS.keys.to_set - METHODS_WITHOUT_INHERIT_PARAM).freeze
77
+
78
+ RESTRICT_ON_SEND = METHOD_REPLACEMENTS.keys.freeze
79
+
80
+ def on_send(node)
45
81
  return unless (parent = node.parent)
46
- return unless instance_methods_inclusion?(parent)
82
+ return unless module_member_inclusion?(parent)
47
83
  return unless simple_method_argument?(node) && simple_method_argument?(parent)
84
+ return if allowed_method?(node.method_name)
48
85
 
49
86
  offense_range = node.location.selector.join(parent.source_range.end)
50
- replacement =
51
- if node.first_argument.nil? || node.first_argument.true_type?
52
- "method_defined?(#{parent.first_argument.source})"
53
- else
54
- "method_defined?(#{parent.first_argument.source}, #{node.first_argument.source})"
55
- end
87
+ replacement = replacement_for(node, parent)
56
88
 
57
89
  add_offense(offense_range, message: format(MSG, replacement: replacement)) do |corrector|
58
90
  corrector.replace(offense_range, replacement)
@@ -62,6 +94,17 @@ module RuboCop
62
94
 
63
95
  private
64
96
 
97
+ def replacement_for(node, parent)
98
+ replacement_method = METHOD_REPLACEMENTS.fetch(node.method_name)
99
+ without_inherit_param = METHODS_WITHOUT_INHERIT_PARAM.include?(node.method_name)
100
+
101
+ if without_inherit_param || node.first_argument.nil? || node.first_argument.true_type?
102
+ "#{replacement_method}(#{parent.first_argument.source})"
103
+ else
104
+ "#{replacement_method}(#{parent.first_argument.source}, #{node.first_argument.source})"
105
+ end
106
+ end
107
+
65
108
  def simple_method_argument?(node)
66
109
  return false if node.splat_argument? || node.block_argument?
67
110
  return false if node.first_argument&.hash_type?
@@ -75,6 +75,8 @@ module RuboCop
75
75
  end
76
76
 
77
77
  def correction_exceeds_max_line_length?(node)
78
+ return false unless max_line_length
79
+
78
80
  indentation_width(node) + definition_width(node) > max_line_length
79
81
  end
80
82
 
@@ -0,0 +1,218 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Style
6
+ # Identifies usages of `arr[arr.length - n]`, `arr[arr.size - n]`, or
7
+ # `arr[arr.count - n]` and suggests to change them to use `arr[-n]` instead.
8
+ # Also handles range patterns like `arr[0..(arr.length - n)]`.
9
+ #
10
+ # The cop recognizes preserving methods (`sort`, `reverse`, `shuffle`, `rotate`)
11
+ # and their combinations, allowing safe replacement when the receiver matches.
12
+ # It works with variables, instance variables, class variables, and constants.
13
+ #
14
+ # @example
15
+ # # bad
16
+ # arr[arr.count - 2]
17
+ # arr[0..(arr.length - 2)]
18
+ # arr[0...(arr.length - 4)]
19
+ # arr.sort[arr.reverse.length - 2]
20
+ # arr.sort.reverse[arr.sort.size - 2]
21
+ #
22
+ # # good
23
+ # arr[-2]
24
+ # arr[0..-2]
25
+ # arr[0...-4]
26
+ # arr.sort[-2]
27
+ # arr.sort.reverse[-2]
28
+ #
29
+ class NegativeArrayIndex < Base
30
+ extend AutoCorrector
31
+ include RangeHelp
32
+
33
+ MSG = 'Use `%<receiver>s[-%<index>s]` instead of `%<current>s`.'
34
+ MSG_RANGE = 'Use `%<receiver>s[%<start>s%<range_op>s-%<index>s]` instead of `%<current>s`.'
35
+ RESTRICT_ON_SEND = %i[[]].freeze
36
+
37
+ LENGTH_METHODS = %i[length size count].freeze
38
+
39
+ PRESERVING_METHODS = %i[sort reverse shuffle rotate].freeze
40
+
41
+ # @!method length_subtraction?(node)
42
+ def_node_matcher :length_subtraction?, <<~PATTERN
43
+ (send
44
+ (send $_ {:length :size :count}) :-
45
+ (int $_))
46
+ PATTERN
47
+
48
+ def on_send(node)
49
+ return if node.arguments.empty?
50
+
51
+ index_arg = node.first_argument
52
+ range_node = extract_range_from_begin(index_arg)
53
+ if range_with_length_subtraction?(range_node, node.receiver)
54
+ receiver = node.receiver.source
55
+ return handle_range_pattern(receiver, range_node, index_arg)
56
+ end
57
+
58
+ handle_simple_index_pattern(node, index_arg)
59
+ end
60
+
61
+ alias on_csend on_send
62
+
63
+ private
64
+
65
+ def handle_simple_index_pattern(node, index_arg)
66
+ length_receiver, negative_index = length_subtraction?(index_arg)
67
+
68
+ return unless negative_index&.positive?
69
+ return unless receivers_match?(length_receiver, node.receiver)
70
+
71
+ add_offense_for_subtraction(node, index_arg, negative_index)
72
+ end
73
+
74
+ def extract_range_from_begin(node)
75
+ node.begin_type? ? node.children.first : node
76
+ end
77
+
78
+ def extract_inner_end(node)
79
+ node.children.size == 1 ? node.children.first : node
80
+ end
81
+
82
+ def add_offense_for_subtraction(node, index_arg, negative_index)
83
+ receiver = node.receiver.source
84
+ offense_range = index_arg.source_range
85
+ current = "#{receiver}[#{index_arg.source}]"
86
+
87
+ message = format(MSG, receiver: receiver, index: negative_index, current: current)
88
+
89
+ add_offense(offense_range, message: message) do |corrector|
90
+ corrector.replace(offense_range, "-#{negative_index}")
91
+ end
92
+ end
93
+
94
+ def range_with_length_subtraction?(range_node, array_receiver)
95
+ return false unless range_node.range_type?
96
+
97
+ range_end = range_node.end
98
+ range_start = range_node.begin
99
+ return false unless range_end && range_start
100
+
101
+ return false unless preserving_method?(range_start)
102
+
103
+ inner_end = extract_inner_end(range_end)
104
+ length_receiver, negative_index = length_subtraction?(inner_end)
105
+
106
+ return false unless negative_index&.positive?
107
+
108
+ receivers_match_strict?(length_receiver, array_receiver)
109
+ end
110
+
111
+ def handle_range_pattern(receiver, range_node, index_arg)
112
+ range_end = range_node.end
113
+ inner_end = extract_inner_end(range_end)
114
+ _length_receiver, negative_index = length_subtraction?(inner_end)
115
+
116
+ message, replacement = build_range_offense_data(
117
+ receiver, range_node, range_end, inner_end, negative_index, index_arg
118
+ )
119
+
120
+ add_offense(range_end, message: message) do |corrector|
121
+ corrector.replace(index_arg, replacement)
122
+ end
123
+ end
124
+
125
+ # rubocop:disable Metrics/ParameterLists
126
+ def build_range_offense_data(receiver, range_node, range_end, inner_end, negative_index,
127
+ index_arg)
128
+ range_op = range_node.erange_type? ? '...' : '..'
129
+ range_start = range_node.begin.source
130
+
131
+ range_without_parens =
132
+ build_range_without_parens(range_start, range_op, range_end, inner_end)
133
+ current_source = build_current_source(receiver, range_without_parens, index_arg)
134
+ start, index = format_range_message_parts(range_start, negative_index, index_arg)
135
+
136
+ message = build_message_for_range(receiver, start, range_op, index, current_source)
137
+ replacement = build_replacement_string(range_start, range_op, negative_index, index_arg)
138
+
139
+ [message, replacement]
140
+ end
141
+ # rubocop:enable Metrics/ParameterLists
142
+
143
+ def format_range_message_parts(range_start, negative_index, index_arg)
144
+ has_parentheses = index_arg.begin_type?
145
+ start = has_parentheses ? "(#{range_start}" : range_start
146
+ index = has_parentheses ? "#{negative_index})" : negative_index
147
+
148
+ [start, index]
149
+ end
150
+
151
+ def build_message_for_range(receiver, start, range_op, index, current)
152
+ format(
153
+ MSG_RANGE,
154
+ receiver: receiver, start: start, range_op: range_op, index: index, current: current
155
+ )
156
+ end
157
+
158
+ def build_replacement_string(range_start, range_op, negative_index, index_arg)
159
+ has_parentheses = index_arg.begin_type?
160
+
161
+ if has_parentheses
162
+ "(#{range_start}#{range_op}-#{negative_index})"
163
+ else
164
+ "#{range_start}#{range_op}-#{negative_index}"
165
+ end
166
+ end
167
+
168
+ def build_current_source(receiver, range_without_parens, index_arg)
169
+ has_parentheses = index_arg.begin_type?
170
+
171
+ if has_parentheses
172
+ "#{receiver}[(#{range_without_parens})]"
173
+ else
174
+ "#{receiver}[#{range_without_parens}]"
175
+ end
176
+ end
177
+
178
+ def build_range_without_parens(range_start, range_op, range_end, inner_end)
179
+ end_expression = range_end.begin_type? ? range_end.source : inner_end.source
180
+
181
+ "#{range_start}#{range_op}#{end_expression}"
182
+ end
183
+
184
+ def receivers_match?(length_receiver, array_receiver)
185
+ unless preserving_method?(array_receiver) && preserving_method?(length_receiver)
186
+ return false
187
+ end
188
+ return true if length_receiver.source == array_receiver.source
189
+
190
+ !extract_base_receiver(array_receiver).nil?
191
+ end
192
+
193
+ def receivers_match_strict?(length_receiver, array_receiver)
194
+ preserving_method?(array_receiver) &&
195
+ length_receiver.source == array_receiver.source
196
+ end
197
+
198
+ def extract_base_receiver(node)
199
+ receiver = node.receiver
200
+
201
+ return nil unless receiver
202
+ return receiver unless receiver.receiver
203
+
204
+ extract_base_receiver(receiver)
205
+ end
206
+
207
+ def preserving_method?(node)
208
+ return true if node.receiver.nil?
209
+
210
+ method_name = node.method_name
211
+ return false unless PRESERVING_METHODS.include?(method_name)
212
+
213
+ preserving_method?(node.receiver)
214
+ end
215
+ end
216
+ end
217
+ end
218
+ end
@@ -26,9 +26,10 @@ module RuboCop
26
26
  splat kwsplat forwarded_args forwarded_restarg forwarded_kwrestarg block_pass
27
27
  ].freeze
28
28
 
29
- # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity
29
+ # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
30
30
  def on_send(node)
31
31
  return unless (dot = node.loc.dot)
32
+ return if unary_method_no_operator?(node)
32
33
  return if node.receiver.const_type? || !node.arguments.one?
33
34
 
34
35
  return unless (rhs = node.first_argument)
@@ -43,10 +44,18 @@ module RuboCop
43
44
  corrector.insert_after(selector, ' ') if insert_space_after?(node)
44
45
  end
45
46
  end
46
- # rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity
47
+ # rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
47
48
 
48
49
  private
49
50
 
51
+ # `foo.~@` and `foo.!@` call the method `~` and `!` respectively. While those
52
+ # are operator methods, we don't want to actually consider them as such.
53
+ def unary_method_no_operator?(node)
54
+ return false unless node.nonmutating_unary_operator_method?
55
+
56
+ node.method_name.to_s != node.selector.source
57
+ end
58
+
50
59
  # Checks for an acceptable case of `foo.+(bar).baz`.
51
60
  def method_call_with_parenthesized_arg?(argument)
52
61
  return false unless argument.parent.parent&.send_type?
@@ -14,22 +14,22 @@ module RuboCop
14
14
  # is a `Hash` or responds to the replacement methods.
15
15
  #
16
16
  # @example EnforcedStyle: short (default)
17
- # # bad
18
- # Hash#has_key?
19
- # Hash#has_value?
17
+ # # bad
18
+ # Hash#has_key?
19
+ # Hash#has_value?
20
20
  #
21
- # # good
22
- # Hash#key?
23
- # Hash#value?
21
+ # # good
22
+ # Hash#key?
23
+ # Hash#value?
24
24
  #
25
25
  # @example EnforcedStyle: verbose
26
- # # bad
27
- # Hash#key?
28
- # Hash#value?
26
+ # # bad
27
+ # Hash#key?
28
+ # Hash#value?
29
29
  #
30
- # # good
31
- # Hash#has_key?
32
- # Hash#has_value?
30
+ # # good
31
+ # Hash#has_key?
32
+ # Hash#has_value?
33
33
  class PreferredHashMethods < Base
34
34
  include ConfigurableEnforcedStyle
35
35
  extend AutoCorrector
@@ -200,7 +200,7 @@ module RuboCop
200
200
  end
201
201
 
202
202
  def asgn_type?(node)
203
- node.type?(:lvasgn, :ivasgn, :cvasgn, :gvasgn)
203
+ node.type?(:lvasgn, :ivasgn, :cvasgn, :gvasgn, :casgn)
204
204
  end
205
205
 
206
206
  def branches_have_method?(node)
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Style
6
+ # Identifies places where `array.reverse.find` can be replaced by `array.rfind`.
7
+ #
8
+ # @safety
9
+ # This cop is unsafe because it cannot be guaranteed that the receiver
10
+ # is an `Array` or responds to the replacement method.
11
+ #
12
+ # @example
13
+ # # bad
14
+ # array.reverse.find { |item| item.even? }
15
+ # array.reverse.detect { |item| item.even? }
16
+ # array.reverse_each.find { |item| item.even? }
17
+ # array.reverse_each.detect { |item| item.even? }
18
+ #
19
+ # # good
20
+ # array.rfind { |item| item.even? }
21
+ #
22
+ class ReverseFind < Base
23
+ extend AutoCorrector
24
+ extend TargetRubyVersion
25
+
26
+ MSG = 'Use `rfind` instead.'
27
+ RESTRICT_ON_SEND = %i[find detect].freeze
28
+
29
+ minimum_target_ruby_version 4.0
30
+
31
+ # @!method reverse_find?(node)
32
+ def_node_matcher :reverse_find?, <<~PATTERN
33
+ (call
34
+ (call
35
+ _ {:reverse :reverse_each}) {:find :detect} (block_pass sym)?)
36
+ PATTERN
37
+
38
+ def on_send(node)
39
+ return unless reverse_find?(node)
40
+
41
+ range = node.children.first.loc.selector.join(node.loc.selector)
42
+
43
+ add_offense(range) do |corrector|
44
+ corrector.replace(range, 'rfind')
45
+ end
46
+ end
47
+ alias on_csend on_send
48
+ end
49
+ end
50
+ end
51
+ end
@@ -211,10 +211,10 @@ module RuboCop
211
211
 
212
212
  each_corrector(report) do |to_merge|
213
213
  suppress_clobbering do
214
- if offset.positive?
215
- corrector.import!(to_merge, offset: offset)
216
- else
214
+ if corrector.source_buffer == to_merge.source_buffer
217
215
  corrector.merge!(to_merge)
216
+ else
217
+ corrector.import!(to_merge, offset: offset)
218
218
  end
219
219
  end
220
220
  end
@@ -254,8 +254,8 @@ module RuboCop
254
254
  end
255
255
  end
256
256
 
257
- # Mix-in module for logical operator control structures.
258
- module LogicalOperator
257
+ # Mix-in module for operator control structures.
258
+ module Operator
259
259
  def always_run?
260
260
  left_body?
261
261
  end
@@ -263,7 +263,15 @@ module RuboCop
263
263
 
264
264
  # left_body && right_body
265
265
  class And < Base
266
- include LogicalOperator
266
+ include Operator
267
+
268
+ define_predicate :left_body?, child_index: 0
269
+ define_predicate :right_body?, child_index: 1
270
+ end
271
+
272
+ # left_body &&= right_body
273
+ class AndAsgn < Base
274
+ include Operator
267
275
 
268
276
  define_predicate :left_body?, child_index: 0
269
277
  define_predicate :right_body?, child_index: 1
@@ -271,7 +279,23 @@ module RuboCop
271
279
 
272
280
  # left_body || right_body
273
281
  class Or < Base
274
- include LogicalOperator
282
+ include Operator
283
+
284
+ define_predicate :left_body?, child_index: 0
285
+ define_predicate :right_body?, child_index: 1
286
+ end
287
+
288
+ # left_body ||= right_body
289
+ class OrAsgn < Base
290
+ include Operator
291
+
292
+ define_predicate :left_body?, child_index: 0
293
+ define_predicate :right_body?, child_index: 1
294
+ end
295
+
296
+ # e.g. left_body += right_body
297
+ class OpAsgn < Base
298
+ include Operator
275
299
 
276
300
  define_predicate :left_body?, child_index: 0
277
301
  define_predicate :right_body?, child_index: 1
@@ -47,8 +47,11 @@ module RuboCop
47
47
  def report_highlighted_area(highlighted_area)
48
48
  space_area = highlighted_area.source_buffer.slice(0...highlighted_area.begin_pos)
49
49
  source_area = highlighted_area.source
50
- output.puts("#{' ' * Unicode::DisplayWidth.of(space_area)}" \
51
- "#{'^' * Unicode::DisplayWidth.of(source_area)}")
50
+ output.puts("#{to_whitespace(space_area)}#{'^' * Unicode::DisplayWidth.of(source_area)}")
51
+ end
52
+
53
+ def to_whitespace(string)
54
+ "#{string.delete("^\t")}#{' ' * Unicode::DisplayWidth.of(string.delete("\t"))}"
52
55
  end
53
56
  end
54
57
  end
@@ -86,7 +86,7 @@ module RuboCop
86
86
 
87
87
  def builtin_formatter_class(specified_key)
88
88
  matching_keys = BUILTIN_FORMATTERS_FOR_KEYS.keys.select do |key|
89
- /^\[#{specified_key}\]/.match?(key) || specified_key == key.delete('[]')
89
+ key.start_with?("[#{specified_key}]") || specified_key == key.delete('[]')
90
90
  end
91
91
 
92
92
  if matching_keys.empty?
@@ -39,8 +39,11 @@ module RuboCop
39
39
  def report_highlighted_area(highlighted_area)
40
40
  space_area = highlighted_area.source_buffer.slice(0...highlighted_area.begin_pos)
41
41
  source_area = highlighted_area.source
42
- output.puts("# #{' ' * Unicode::DisplayWidth.of(space_area)}" \
43
- "#{'^' * Unicode::DisplayWidth.of(source_area)}")
42
+ output.puts("# #{to_whitespace(space_area)}#{'^' * Unicode::DisplayWidth.of(source_area)}")
43
+ end
44
+
45
+ def to_whitespace(string)
46
+ "#{string.delete("^\t")}#{' ' * Unicode::DisplayWidth.of(string.delete("\t"))}"
44
47
  end
45
48
 
46
49
  def report_offense(file, offense)
@@ -81,7 +81,7 @@ module RuboCop
81
81
  end
82
82
 
83
83
  def cache_path
84
- @cache_path ||= File.expand_path(".rubocop-remote-#{cache_name_from_uri}", @cache_root)
84
+ @cache_path ||= File.expand_path(cache_name_from_uri, @cache_root)
85
85
  end
86
86
 
87
87
  def cache_path_exists?
@@ -98,7 +98,10 @@ module RuboCop
98
98
  end
99
99
 
100
100
  def cache_name_from_uri
101
- "#{Digest::MD5.hexdigest(@uri.to_s)}.yml"
101
+ # The md5 checksum suffix is 37 bytes, so we play it save and
102
+ # allow 254 bytes total - this should be safe on Linux/macOS/Windows
103
+ filename = File.basename(@uri.path).gsub(/\.ya?ml\z/i, '').byteslice(0, 217).scrub('')
104
+ "#{filename}-#{Digest::MD5.hexdigest(@uri.to_s)}.yml"
102
105
  end
103
106
 
104
107
  def cloned_url