rubocop 1.37.1 → 1.39.0

Sign up to get free protection for your applications and to get access to all the features.
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