rubocop 1.37.1 → 1.39.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 (53) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  3. data/config/default.yml +10 -2
  4. data/lib/rubocop/comment_config.rb +36 -1
  5. data/lib/rubocop/cop/commissioner.rb +3 -1
  6. data/lib/rubocop/cop/layout/indentation_style.rb +1 -1
  7. data/lib/rubocop/cop/layout/line_continuation_leading_space.rb +29 -8
  8. data/lib/rubocop/cop/layout/line_continuation_spacing.rb +1 -1
  9. data/lib/rubocop/cop/layout/space_inside_array_percent_literal.rb +3 -0
  10. data/lib/rubocop/cop/layout/space_inside_hash_literal_braces.rb +2 -0
  11. data/lib/rubocop/cop/layout/space_inside_percent_literal_delimiters.rb +34 -0
  12. data/lib/rubocop/cop/layout/space_inside_reference_brackets.rb +6 -2
  13. data/lib/rubocop/cop/lint/duplicate_methods.rb +17 -8
  14. data/lib/rubocop/cop/lint/empty_conditional_body.rb +1 -1
  15. data/lib/rubocop/cop/lint/missing_cop_enable_directive.rb +18 -3
  16. data/lib/rubocop/cop/lint/redundant_cop_disable_directive.rb +11 -0
  17. data/lib/rubocop/cop/lint/redundant_require_statement.rb +10 -2
  18. data/lib/rubocop/cop/lint/suppressed_exception.rb +1 -1
  19. data/lib/rubocop/cop/metrics/abc_size.rb +1 -1
  20. data/lib/rubocop/cop/mixin/range_help.rb +23 -0
  21. data/lib/rubocop/cop/mixin/surrounding_space.rb +4 -3
  22. data/lib/rubocop/cop/mixin/visibility_help.rb +40 -5
  23. data/lib/rubocop/cop/registry.rb +10 -4
  24. data/lib/rubocop/cop/style/access_modifier_declarations.rb +1 -25
  25. data/lib/rubocop/cop/style/block_delimiters.rb +1 -1
  26. data/lib/rubocop/cop/style/class_equality_comparison.rb +7 -5
  27. data/lib/rubocop/cop/style/collection_compact.rb +4 -2
  28. data/lib/rubocop/cop/style/guard_clause.rb +62 -21
  29. data/lib/rubocop/cop/style/hash_as_last_array_item.rb +1 -0
  30. data/lib/rubocop/cop/style/hash_each_methods.rb +32 -10
  31. data/lib/rubocop/cop/style/hash_except.rb +4 -0
  32. data/lib/rubocop/cop/style/if_with_boolean_literal_branches.rb +25 -2
  33. data/lib/rubocop/cop/style/module_function.rb +28 -6
  34. data/lib/rubocop/cop/style/object_then.rb +3 -0
  35. data/lib/rubocop/cop/style/operator_method_call.rb +13 -0
  36. data/lib/rubocop/cop/style/quoted_symbols.rb +1 -1
  37. data/lib/rubocop/cop/style/redundant_each.rb +116 -0
  38. data/lib/rubocop/cop/style/redundant_regexp_escape.rb +12 -3
  39. data/lib/rubocop/cop/team.rb +3 -4
  40. data/lib/rubocop/cop/util.rb +1 -1
  41. data/lib/rubocop/cop/variable_force/variable_table.rb +1 -1
  42. data/lib/rubocop/cops_documentation_generator.rb +2 -1
  43. data/lib/rubocop/ext/processed_source.rb +2 -0
  44. data/lib/rubocop/formatter/offense_count_formatter.rb +8 -5
  45. data/lib/rubocop/formatter/worst_offenders_formatter.rb +6 -3
  46. data/lib/rubocop/options.rb +6 -2
  47. data/lib/rubocop/rspec/cop_helper.rb +21 -1
  48. data/lib/rubocop/rspec/shared_contexts.rb +13 -12
  49. data/lib/rubocop/runner.rb +15 -11
  50. data/lib/rubocop/server/core.rb +16 -0
  51. data/lib/rubocop/version.rb +1 -1
  52. data/lib/rubocop.rb +1 -0
  53. metadata +4 -3
@@ -149,16 +149,22 @@ module RuboCop
149
149
  @registry.size
150
150
  end
151
151
 
152
- def enabled(config, only = [], only_safe: false)
153
- select { |cop| only.include?(cop.cop_name) || enabled?(cop, config, only_safe) }
152
+ def enabled(config)
153
+ select { |cop| enabled?(cop, config) }
154
154
  end
155
155
 
156
- def enabled?(cop, config, only_safe)
156
+ def disabled(config)
157
+ reject { |cop| enabled?(cop, config) }
158
+ end
159
+
160
+ def enabled?(cop, config)
161
+ return true if options.fetch(:only, []).include?(cop.cop_name)
162
+
157
163
  cfg = config.for_cop(cop)
158
164
 
159
165
  cop_enabled = cfg.fetch('Enabled') == true || enabled_pending_cop?(cfg, config)
160
166
 
161
- if only_safe
167
+ if options.fetch(:safe, false)
162
168
  cop_enabled && cfg.fetch('Safe', true)
163
169
  else
164
170
  cop_enabled
@@ -202,31 +202,7 @@ module RuboCop
202
202
  end
203
203
 
204
204
  def remove_node(corrector, node)
205
- corrector.remove(
206
- range_by_whole_lines(
207
- range_with_comments(node),
208
- include_final_newline: true
209
- )
210
- )
211
- end
212
-
213
- def range_with_comments(node)
214
- ranges = [
215
- node,
216
- *processed_source.ast_with_comments[node]
217
- ].map do |element|
218
- element.location.expression
219
- end
220
- ranges.reduce do |result, range|
221
- add_range(result, range)
222
- end
223
- end
224
-
225
- def add_range(range1, range2)
226
- range1.with(
227
- begin_pos: [range1.begin_pos, range2.begin_pos].min,
228
- end_pos: [range1.end_pos, range2.end_pos].max
229
- )
205
+ corrector.remove(range_with_comments_and_lines(node))
230
206
  end
231
207
  end
232
208
  end
@@ -425,7 +425,7 @@ module RuboCop
425
425
  if node.parent.begin_type?
426
426
  return_value_used?(node.parent)
427
427
  else
428
- node.parent.assignment? || node.parent.send_type?
428
+ node.parent.assignment? || node.parent.call_type?
429
429
  end
430
430
  end
431
431
 
@@ -97,12 +97,14 @@ module RuboCop
97
97
  if node.children.first.method?(:name)
98
98
  return class_node.receiver.source if class_node.receiver
99
99
 
100
- value = class_node.source.delete('"').delete("'")
101
- value.prepend('::') if class_node.each_ancestor(:class, :module).any?
102
- value
103
- else
104
- class_node.source
100
+ if class_node.str_type?
101
+ value = class_node.source.delete('"').delete("'")
102
+ value.prepend('::') if class_node.each_ancestor(:class, :module).any?
103
+ return value
104
+ end
105
105
  end
106
+
107
+ class_node.source
106
108
  end
107
109
 
108
110
  def offense_range(receiver_node, node)
@@ -8,7 +8,9 @@ module RuboCop
8
8
  #
9
9
  # @safety
10
10
  # It is unsafe by default because false positives may occur in the
11
- # `nil` check of block arguments to the receiver object.
11
+ # `nil` check of block arguments to the receiver object. Additionally,
12
+ # we can't know the type of the receiver object for sure, which may
13
+ # result in false positives as well.
12
14
  #
13
15
  # For example, `[[1, 2], [3, nil]].reject { |first, second| second.nil? }`
14
16
  # and `[[1, 2], [3, nil]].compact` are not compatible. This will work fine
@@ -110,7 +112,7 @@ module RuboCop
110
112
  end
111
113
 
112
114
  def range(begin_pos_node, end_pos_node)
113
- range_between(begin_pos_node.loc.selector.begin_pos, end_pos_node.loc.end.end_pos)
115
+ range_between(begin_pos_node.loc.selector.begin_pos, end_pos_node.loc.expression.end_pos)
114
116
  end
115
117
  end
116
118
  end
@@ -10,6 +10,9 @@ module RuboCop
10
10
  # one of `return`, `break`, `next`, `raise`, or `fail` is used
11
11
  # in the body of the conditional expression.
12
12
  #
13
+ # NOTE: Autocorrect works in most cases except with if-else statements
14
+ # that contain logical operators such as `foo || raise('exception')`
15
+ #
13
16
  # @example
14
17
  # # bad
15
18
  # def test
@@ -90,6 +93,7 @@ module RuboCop
90
93
  # end
91
94
  #
92
95
  class GuardClause < Base
96
+ extend AutoCorrector
93
97
  include MinBodyLength
94
98
  include StatementModifier
95
99
 
@@ -101,40 +105,49 @@ module RuboCop
101
105
 
102
106
  return unless body
103
107
 
104
- if body.if_type?
105
- check_ending_if(body)
106
- elsif body.begin_type?
107
- final_expression = body.children.last
108
- check_ending_if(final_expression) if final_expression&.if_type?
109
- end
108
+ check_ending_body(body)
110
109
  end
111
110
  alias on_defs on_def
112
111
 
113
112
  def on_if(node)
114
113
  return if accepted_form?(node)
115
114
 
116
- guard_clause_in_if = node.if_branch&.guard_clause?
117
- guard_clause_in_else = node.else_branch&.guard_clause?
118
- guard_clause = guard_clause_in_if || guard_clause_in_else
119
- return unless guard_clause
115
+ if (guard_clause = node.if_branch&.guard_clause?)
116
+ kw = node.loc.keyword.source
117
+ guard = :if
118
+ elsif (guard_clause = node.else_branch&.guard_clause?)
119
+ kw = node.inverse_keyword
120
+ guard = :else
121
+ else
122
+ return
123
+ end
120
124
 
121
- kw = if guard_clause_in_if
122
- node.loc.keyword.source
123
- else
124
- node.inverse_keyword
125
- end
125
+ guard = nil if and_or_guard_clause?(guard_clause)
126
126
 
127
- register_offense(node, guard_clause_source(guard_clause), kw)
127
+ register_offense(node, guard_clause_source(guard_clause), kw, guard)
128
128
  end
129
129
 
130
130
  private
131
131
 
132
+ def check_ending_body(body)
133
+ return if body.nil?
134
+
135
+ if body.if_type?
136
+ check_ending_if(body)
137
+ elsif body.begin_type?
138
+ final_expression = body.children.last
139
+ check_ending_if(final_expression) if final_expression&.if_type?
140
+ end
141
+ end
142
+
132
143
  def check_ending_if(node)
133
144
  return if accepted_form?(node, ending: true) || !min_body_length?(node)
134
145
  return if allowed_consecutive_conditionals? &&
135
146
  consecutive_conditionals?(node.parent, node)
136
147
 
137
148
  register_offense(node, 'return', node.inverse_keyword)
149
+
150
+ check_ending_body(node.if_branch)
138
151
  end
139
152
 
140
153
  def consecutive_conditionals?(parent, node)
@@ -145,28 +158,56 @@ module RuboCop
145
158
  end
146
159
  end
147
160
 
148
- def register_offense(node, scope_exiting_keyword, conditional_keyword)
161
+ def register_offense(node, scope_exiting_keyword, conditional_keyword, guard = nil)
149
162
  condition, = node.node_parts
150
163
  example = [scope_exiting_keyword, conditional_keyword, condition.source].join(' ')
151
164
  if too_long_for_single_line?(node, example)
152
165
  return if trivial?(node)
153
166
 
154
167
  example = "#{conditional_keyword} #{condition.source}; #{scope_exiting_keyword}; end"
168
+ replacement = <<~RUBY.chomp
169
+ #{conditional_keyword} #{condition.source}
170
+ #{scope_exiting_keyword}
171
+ end
172
+ RUBY
155
173
  end
156
174
 
157
- add_offense(node.loc.keyword, message: format(MSG, example: example))
175
+ add_offense(node.loc.keyword, message: format(MSG, example: example)) do |corrector|
176
+ next if node.else? && guard.nil?
177
+
178
+ autocorrect(corrector, node, condition, replacement || example, guard)
179
+ end
158
180
  end
159
181
 
160
- def guard_clause_source(guard_clause)
161
- parent = guard_clause.parent
182
+ def autocorrect(corrector, node, condition, replacement, guard)
183
+ corrector.replace(node.loc.keyword.join(condition.loc.expression), replacement)
184
+ corrector.remove(node.loc.end)
185
+ return unless node.else?
186
+
187
+ corrector.remove(node.loc.else)
188
+ corrector.remove(branch_to_remove(node, guard))
189
+ end
190
+
191
+ def branch_to_remove(node, guard)
192
+ case guard
193
+ when :if then node.if_branch
194
+ when :else then node.else_branch
195
+ end
196
+ end
162
197
 
163
- if parent.and_type? || parent.or_type?
198
+ def guard_clause_source(guard_clause)
199
+ if and_or_guard_clause?(guard_clause)
164
200
  guard_clause.parent.source
165
201
  else
166
202
  guard_clause.source
167
203
  end
168
204
  end
169
205
 
206
+ def and_or_guard_clause?(guard_clause)
207
+ parent = guard_clause.parent
208
+ parent.and_type? || parent.or_type?
209
+ end
210
+
170
211
  def too_long_for_single_line?(node, example)
171
212
  max = max_line_length
172
213
  max && node.source_range.column + example.length > max
@@ -34,6 +34,7 @@ module RuboCop
34
34
  extend AutoCorrector
35
35
 
36
36
  def on_hash(node)
37
+ return if node.children.first&.kwsplat_type?
37
38
  return unless (array = containing_array(node))
38
39
  return unless last_array_item?(array, node) && explicit_array?(array)
39
40
 
@@ -38,28 +38,50 @@ module RuboCop
38
38
  ({block numblock} $(send (send _ ${:keys :values}) :each) ...)
39
39
  PATTERN
40
40
 
41
+ # @!method kv_each_with_block_pass(node)
42
+ def_node_matcher :kv_each_with_block_pass, <<~PATTERN
43
+ (send $(send _ ${:keys :values}) :each (block_pass (sym _)))
44
+ PATTERN
45
+
41
46
  def on_block(node)
42
- register_kv_offense(node)
47
+ kv_each(node) do |target, method|
48
+ register_kv_offense(target, method)
49
+ end
43
50
  end
44
51
 
45
52
  alias on_numblock on_block
46
53
 
54
+ def on_block_pass(node)
55
+ kv_each_with_block_pass(node.parent) do |target, method|
56
+ register_kv_with_block_pass_offense(node, target, method)
57
+ end
58
+ end
59
+
47
60
  private
48
61
 
49
- def register_kv_offense(node)
50
- kv_each(node) do |target, method|
51
- parent_receiver = target.receiver.receiver
52
- return unless parent_receiver
53
- return if allowed_receiver?(parent_receiver)
62
+ def register_kv_offense(target, method)
63
+ return unless (parent_receiver = target.receiver.receiver)
64
+ return if allowed_receiver?(parent_receiver)
65
+
66
+ add_offense(kv_range(target), message: format_message(method)) do |corrector|
67
+ correct_key_value_each(target, corrector)
68
+ end
69
+ end
54
70
 
55
- msg = format(message, prefer: "each_#{method[0..-2]}", current: "#{method}.each")
71
+ def register_kv_with_block_pass_offense(node, target, method)
72
+ return unless (parent_receiver = node.parent.receiver.receiver)
73
+ return if allowed_receiver?(parent_receiver)
56
74
 
57
- add_offense(kv_range(target), message: msg) do |corrector|
58
- correct_key_value_each(target, corrector)
59
- end
75
+ range = target.loc.selector.with(end_pos: node.parent.loc.selector.end_pos)
76
+ add_offense(range, message: format_message(method)) do |corrector|
77
+ corrector.replace(range, "each_#{method[0..-2]}")
60
78
  end
61
79
  end
62
80
 
81
+ def format_message(method_name)
82
+ format(MSG, prefer: "each_#{method_name[0..-2]}", current: "#{method_name}.each")
83
+ end
84
+
63
85
  def check_argument(variable)
64
86
  return unless variable.block_argument?
65
87
 
@@ -13,6 +13,10 @@ module RuboCop
13
13
  # when used `==`.
14
14
  # And do not check `Hash#delete_if` and `Hash#keep_if` to change receiver object.
15
15
  #
16
+ # @safety
17
+ # This cop is unsafe because it cannot be guaranteed that the receiver
18
+ # is a `Hash` or responds to the replacement method.
19
+ #
16
20
  # @example
17
21
  #
18
22
  # # bad
@@ -5,10 +5,27 @@ module RuboCop
5
5
  module Style
6
6
  # Checks for redundant `if` with boolean literal branches.
7
7
  # It checks only conditions to return boolean value (`true` or `false`) for safe detection.
8
- # The conditions to be checked are comparison methods, predicate methods, and double negative.
8
+ # The conditions to be checked are comparison methods, predicate methods, and
9
+ # double negation (!!).
9
10
  # `nonzero?` method is allowed by default.
10
11
  # These are customizable with `AllowedMethods` option.
11
12
  #
13
+ # This cop targets only `if`s with a single `elsif` or `else` branch. The following
14
+ # code will be allowed, because it has two `elsif` branches:
15
+ #
16
+ # [source,ruby]
17
+ # ----
18
+ # if foo
19
+ # true
20
+ # elsif bar > baz
21
+ # true
22
+ # elsif qux > quux # Single `elsif` is warned, but two or more `elsif`s are not.
23
+ # true
24
+ # else
25
+ # false
26
+ # end
27
+ # ----
28
+ #
12
29
  # @safety
13
30
  # Autocorrection is unsafe because there is no guarantee that all predicate methods
14
31
  # will return a boolean value. Those methods can be allowed with `AllowedMethods` config.
@@ -57,7 +74,7 @@ module RuboCop
57
74
  def_node_matcher :double_negative?, '(send (send _ :!) :!)'
58
75
 
59
76
  def on_if(node)
60
- return unless if_with_boolean_literal_branches?(node)
77
+ return if !if_with_boolean_literal_branches?(node) || multiple_elsif?(node)
61
78
 
62
79
  condition = node.condition
63
80
  range, keyword = offense_range_with_keyword(node, condition)
@@ -76,6 +93,12 @@ module RuboCop
76
93
 
77
94
  private
78
95
 
96
+ def multiple_elsif?(node)
97
+ return false unless (parent = node.parent)
98
+
99
+ parent.if_type? && parent.elsif?
100
+ end
101
+
79
102
  def offense_range_with_keyword(node, condition)
80
103
  if node.ternary?
81
104
  range = condition.source_range.end.join(node.source_range.end)
@@ -3,13 +3,15 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Style
6
- # Checks for use of `extend self` or `module_function` in a
7
- # module.
6
+ # Checks for use of `extend self` or `module_function` in a module.
8
7
  #
9
- # Supported styles are: module_function, extend_self, forbidden. `forbidden`
10
- # style prohibits the usage of both styles.
8
+ # Supported styles are: `module_function` (default), `extend_self` and `forbidden`.
11
9
  #
12
- # NOTE: the cop won't be activated when the module contains any private methods.
10
+ # A couple of things to keep in mind:
11
+ #
12
+ # - `forbidden` style prohibits the usage of both styles
13
+ # - in default mode (`module_function`), the cop won't be activated when the module
14
+ # contains any private methods
13
15
  #
14
16
  # @safety
15
17
  # Autocorrection is unsafe (and is disabled by default) because `extend self`
@@ -28,7 +30,6 @@ module RuboCop
28
30
  # # ...
29
31
  # end
30
32
  #
31
- # @example EnforcedStyle: module_function (default)
32
33
  # # good
33
34
  # module Test
34
35
  # extend self
@@ -37,6 +38,13 @@ module RuboCop
37
38
  # # ...
38
39
  # end
39
40
  #
41
+ # # good
42
+ # module Test
43
+ # class << self
44
+ # # ...
45
+ # end
46
+ # end
47
+ #
40
48
  # @example EnforcedStyle: extend_self
41
49
  # # bad
42
50
  # module Test
@@ -50,6 +58,13 @@ module RuboCop
50
58
  # # ...
51
59
  # end
52
60
  #
61
+ # # good
62
+ # module Test
63
+ # class << self
64
+ # # ...
65
+ # end
66
+ # end
67
+ #
53
68
  # @example EnforcedStyle: forbidden
54
69
  # # bad
55
70
  # module Test
@@ -70,6 +85,13 @@ module RuboCop
70
85
  # private
71
86
  # # ...
72
87
  # end
88
+ #
89
+ # # good
90
+ # module Test
91
+ # class << self
92
+ # # ...
93
+ # end
94
+ # end
73
95
  class ModuleFunction < Base
74
96
  include ConfigurableEnforcedStyle
75
97
  extend AutoCorrector
@@ -25,6 +25,9 @@ module RuboCop
25
25
  class ObjectThen < Base
26
26
  include ConfigurableEnforcedStyle
27
27
  extend AutoCorrector
28
+ extend TargetRubyVersion
29
+
30
+ minimum_target_ruby_version 2.6
28
31
 
29
32
  MSG = 'Prefer `%<prefer>s` over `%<current>s`.'
30
33
 
@@ -31,9 +31,22 @@ module RuboCop
31
31
  return if rhs.nil? || rhs.children.first
32
32
 
33
33
  add_offense(dot) do |corrector|
34
+ wrap_in_parentheses_if_chained(corrector, node)
34
35
  corrector.replace(dot, ' ')
35
36
  end
36
37
  end
38
+
39
+ private
40
+
41
+ def wrap_in_parentheses_if_chained(corrector, node)
42
+ return unless node.parent&.call_type?
43
+
44
+ operator = node.loc.selector
45
+
46
+ ParenthesesCorrector.correct(corrector, node)
47
+ corrector.insert_after(operator, ' ')
48
+ corrector.wrap(node, '(', ')')
49
+ end
37
50
  end
38
51
  end
39
52
  end
@@ -93,7 +93,7 @@ module RuboCop
93
93
  end
94
94
 
95
95
  # The conversion process doubles escaped slashes, so they have to be reverted
96
- correction.gsub('\\\\', '\\')
96
+ correction.gsub('\\\\', '\\').gsub('\"', '"')
97
97
  end
98
98
 
99
99
  def style
@@ -0,0 +1,116 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Style
6
+ # Checks for redundant `each`.
7
+ #
8
+ # @safety
9
+ # This cop is unsafe, as it can produce false positives if the receiver
10
+ # is not an `Enumerator`.
11
+ #
12
+ # @example
13
+ #
14
+ # # bad
15
+ # array.each.each { |v| do_something(v) }
16
+ #
17
+ # # good
18
+ # array.each { |v| do_something(v) }
19
+ #
20
+ # # bad
21
+ # array.each.each_with_index { |v, i| do_something(v, i) }
22
+ #
23
+ # # good
24
+ # array.each.with_index { |v, i| do_something(v, i) }
25
+ # array.each_with_index { |v, i| do_something(v, i) }
26
+ #
27
+ # # bad
28
+ # array.each.each_with_object { |v, o| do_something(v, o) }
29
+ #
30
+ # # good
31
+ # array.each.with_object { |v, o| do_something(v, o) }
32
+ # array.each_with_object { |v, o| do_something(v, o) }
33
+ #
34
+ class RedundantEach < Base
35
+ extend AutoCorrector
36
+
37
+ MSG = 'Remove redundant `each`.'
38
+ MSG_WITH_INDEX = 'Use `with_index` to remove redundant `each`.'
39
+ MSG_WITH_OBJECT = 'Use `with_object` to remove redundant `each`.'
40
+
41
+ RESTRICT_ON_SEND = %i[each each_with_index each_with_object].freeze
42
+
43
+ def on_send(node)
44
+ return unless (redundant_node = redundant_each_method(node))
45
+
46
+ range = range(node)
47
+
48
+ add_offense(range, message: message(node)) do |corrector|
49
+ case node.method_name
50
+ when :each
51
+ remove_redundant_each(corrector, range, redundant_node)
52
+ when :each_with_index
53
+ corrector.replace(node.loc.selector, 'with_index')
54
+ when :each_with_object
55
+ corrector.replace(node.loc.selector, 'with_object')
56
+ end
57
+ end
58
+ end
59
+
60
+ private
61
+
62
+ # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
63
+ def redundant_each_method(node)
64
+ return if node.last_argument&.block_pass_type?
65
+
66
+ if node.method?(:each) && !node.parent&.block_type?
67
+ ancestor_node = node.each_ancestor(:send).detect do |ancestor|
68
+ ancestor.receiver == node &&
69
+ (RESTRICT_ON_SEND.include?(ancestor.method_name) || ancestor.method?(:reverse_each))
70
+ end
71
+
72
+ return ancestor_node if ancestor_node
73
+ end
74
+
75
+ return unless (prev_method = node.children.first)
76
+ return if !prev_method.send_type? ||
77
+ prev_method.parent.block_type? || prev_method.last_argument&.block_pass_type?
78
+
79
+ detected = prev_method.method_name.to_s.start_with?('each_') unless node.method?(:each)
80
+
81
+ prev_method if detected || prev_method.method?(:reverse_each)
82
+ end
83
+ # rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
84
+
85
+ def range(node)
86
+ if node.method?(:each)
87
+ node.loc.dot.join(node.loc.selector)
88
+ else
89
+ node.loc.selector
90
+ end
91
+ end
92
+
93
+ def message(node)
94
+ case node.method_name
95
+ when :each
96
+ MSG
97
+ when :each_with_index
98
+ MSG_WITH_INDEX
99
+ when :each_with_object
100
+ MSG_WITH_OBJECT
101
+ end
102
+ end
103
+
104
+ def remove_redundant_each(corrector, range, redundant_node)
105
+ corrector.remove(range)
106
+
107
+ if redundant_node.method?(:each_with_index)
108
+ corrector.replace(redundant_node.loc.selector, 'each.with_index')
109
+ elsif redundant_node.method?(:each_with_object)
110
+ corrector.replace(redundant_node.loc.selector, 'each.with_object')
111
+ end
112
+ end
113
+ end
114
+ end
115
+ end
116
+ end
@@ -44,7 +44,7 @@ module RuboCop
44
44
 
45
45
  def on_regexp(node)
46
46
  each_escape(node) do |char, index, within_character_class|
47
- next if allowed_escape?(node, char, within_character_class)
47
+ next if allowed_escape?(node, char, index, within_character_class)
48
48
 
49
49
  location = escape_range_at_index(node, index)
50
50
 
@@ -56,7 +56,7 @@ module RuboCop
56
56
 
57
57
  private
58
58
 
59
- def allowed_escape?(node, char, within_character_class)
59
+ def allowed_escape?(node, char, index, within_character_class)
60
60
  # Strictly speaking a few single-letter metachars are currently
61
61
  # unnecessary to "escape", e.g. i, E, F, but enumerating them is
62
62
  # rather difficult, and their behavior could change over time with
@@ -65,12 +65,21 @@ module RuboCop
65
65
  return true if ALLOWED_ALWAYS_ESCAPES.include?(char) || delimiter?(node, char)
66
66
 
67
67
  if within_character_class
68
- ALLOWED_WITHIN_CHAR_CLASS_METACHAR_ESCAPES.include?(char)
68
+ ALLOWED_WITHIN_CHAR_CLASS_METACHAR_ESCAPES.include?(char) &&
69
+ !char_class_begins_or_ends_with_escaped_hyphen?(node, index)
69
70
  else
70
71
  ALLOWED_OUTSIDE_CHAR_CLASS_METACHAR_ESCAPES.include?(char)
71
72
  end
72
73
  end
73
74
 
75
+ def char_class_begins_or_ends_with_escaped_hyphen?(node, index)
76
+ # The hyphen character is allowed to be escaped within a character class
77
+ # but it's not necessry to escape hyphen if it's the first or last character
78
+ # within the character class. This method checks if that's the case.
79
+ # e.g. "[0-9\\-]" or "[\\-0-9]" would return true
80
+ node.source[index] == '[' || node.source[index + 3] == ']'
81
+ end
82
+
74
83
  def delimiter?(node, char)
75
84
  delimiters = [node.loc.begin.source[-1], node.loc.end.source[0]]
76
85
 
@@ -40,10 +40,9 @@ module RuboCop
40
40
 
41
41
  # @return [Array<Cop::Cop>]
42
42
  def self.mobilize_cops(cop_classes, config, options = {})
43
- cop_classes = Registry.new(cop_classes.to_a) unless cop_classes.is_a?(Registry)
44
- only = options.fetch(:only, [])
45
- safe = options.fetch(:safe, false)
46
- cop_classes.enabled(config, only, only_safe: safe).map do |cop_class|
43
+ cop_classes = Registry.new(cop_classes.to_a, options) unless cop_classes.is_a?(Registry)
44
+
45
+ cop_classes.enabled(config).map do |cop_class|
47
46
  cop_class.new(config, options)
48
47
  end
49
48
  end