rubocop 1.38.0 → 1.40.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (83) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  3. data/config/default.yml +51 -11
  4. data/exe/rubocop +1 -1
  5. data/lib/rubocop/comment_config.rb +5 -0
  6. data/lib/rubocop/config.rb +5 -4
  7. data/lib/rubocop/config_loader.rb +5 -5
  8. data/lib/rubocop/config_loader_resolver.rb +1 -1
  9. data/lib/rubocop/cop/base.rb +2 -9
  10. data/lib/rubocop/cop/correctors/multiline_literal_brace_corrector.rb +22 -6
  11. data/lib/rubocop/cop/internal_affairs/lambda_or_proc.rb +46 -0
  12. data/lib/rubocop/cop/internal_affairs.rb +1 -0
  13. data/lib/rubocop/cop/layout/line_continuation_leading_space.rb +29 -8
  14. data/lib/rubocop/cop/layout/space_inside_array_percent_literal.rb +3 -0
  15. data/lib/rubocop/cop/layout/space_inside_percent_literal_delimiters.rb +34 -0
  16. data/lib/rubocop/cop/lint/ambiguous_block_association.rb +1 -1
  17. data/lib/rubocop/cop/lint/assignment_in_condition.rb +11 -1
  18. data/lib/rubocop/cop/lint/deprecated_constants.rb +8 -1
  19. data/lib/rubocop/cop/lint/duplicate_methods.rb +17 -8
  20. data/lib/rubocop/cop/lint/empty_block.rb +1 -5
  21. data/lib/rubocop/cop/lint/empty_conditional_body.rb +1 -1
  22. data/lib/rubocop/cop/lint/interpolation_check.rb +4 -3
  23. data/lib/rubocop/cop/lint/parentheses_as_grouped_expression.rb +5 -0
  24. data/lib/rubocop/cop/lint/redundant_cop_disable_directive.rb +13 -3
  25. data/lib/rubocop/cop/lint/safe_navigation_chain.rb +15 -6
  26. data/lib/rubocop/cop/lint/send_with_mixin_argument.rb +5 -4
  27. data/lib/rubocop/cop/lint/shadowing_outer_local_variable.rb +4 -3
  28. data/lib/rubocop/cop/lint/suppressed_exception.rb +1 -1
  29. data/lib/rubocop/cop/lint/void.rb +6 -6
  30. data/lib/rubocop/cop/metrics/abc_size.rb +1 -1
  31. data/lib/rubocop/cop/metrics/block_length.rb +9 -4
  32. data/lib/rubocop/cop/metrics/class_length.rb +9 -4
  33. data/lib/rubocop/cop/metrics/method_length.rb +9 -4
  34. data/lib/rubocop/cop/metrics/module_length.rb +9 -4
  35. data/lib/rubocop/cop/metrics/utils/code_length_calculator.rb +5 -2
  36. data/lib/rubocop/cop/mixin/hash_shorthand_syntax.rb +24 -5
  37. data/lib/rubocop/cop/mixin/range_help.rb +23 -0
  38. data/lib/rubocop/cop/mixin/statement_modifier.rb +15 -1
  39. data/lib/rubocop/cop/mixin/visibility_help.rb +40 -5
  40. data/lib/rubocop/cop/registry.rb +23 -11
  41. data/lib/rubocop/cop/style/access_modifier_declarations.rb +1 -25
  42. data/lib/rubocop/cop/style/array_intersect.rb +111 -0
  43. data/lib/rubocop/cop/style/class_equality_comparison.rb +7 -5
  44. data/lib/rubocop/cop/style/collection_compact.rb +1 -1
  45. data/lib/rubocop/cop/style/guard_clause.rb +32 -5
  46. data/lib/rubocop/cop/style/hash_as_last_array_item.rb +1 -0
  47. data/lib/rubocop/cop/style/hash_each_methods.rb +32 -10
  48. data/lib/rubocop/cop/style/hash_except.rb +4 -0
  49. data/lib/rubocop/cop/style/if_with_semicolon.rb +2 -1
  50. data/lib/rubocop/cop/style/nil_lambda.rb +1 -1
  51. data/lib/rubocop/cop/style/object_then.rb +3 -0
  52. data/lib/rubocop/cop/style/operator_method_call.rb +13 -0
  53. data/lib/rubocop/cop/style/quoted_symbols.rb +1 -1
  54. data/lib/rubocop/cop/style/redundant_argument.rb +3 -0
  55. data/lib/rubocop/cop/style/redundant_constant_base.rb +72 -0
  56. data/lib/rubocop/cop/style/redundant_each.rb +13 -8
  57. data/lib/rubocop/cop/style/redundant_regexp_escape.rb +12 -3
  58. data/lib/rubocop/cop/style/redundant_return.rb +7 -0
  59. data/lib/rubocop/cop/style/redundant_sort.rb +1 -1
  60. data/lib/rubocop/cop/style/require_order.rb +88 -0
  61. data/lib/rubocop/cop/style/safe_navigation.rb +35 -6
  62. data/lib/rubocop/cop/style/select_by_regexp.rb +8 -4
  63. data/lib/rubocop/cop/style/string_literals.rb +1 -5
  64. data/lib/rubocop/cop/style/symbol_proc.rb +2 -4
  65. data/lib/rubocop/cop/team.rb +1 -1
  66. data/lib/rubocop/cop/util.rb +2 -2
  67. data/lib/rubocop/cop/variable_force/assignment.rb +1 -1
  68. data/lib/rubocop/cop/variable_force.rb +20 -29
  69. data/lib/rubocop/cops_documentation_generator.rb +3 -4
  70. data/lib/rubocop/formatter/disabled_config_formatter.rb +17 -6
  71. data/lib/rubocop/formatter/html_formatter.rb +1 -1
  72. data/lib/rubocop/formatter.rb +3 -1
  73. data/lib/rubocop/optimized_patterns.rb +38 -0
  74. data/lib/rubocop/options.rb +9 -1
  75. data/lib/rubocop/path_util.rb +14 -2
  76. data/lib/rubocop/result_cache.rb +1 -1
  77. data/lib/rubocop/rspec/cop_helper.rb +4 -1
  78. data/lib/rubocop/rspec/support.rb +2 -2
  79. data/lib/rubocop/server/core.rb +16 -1
  80. data/lib/rubocop/target_ruby.rb +1 -1
  81. data/lib/rubocop/version.rb +1 -1
  82. data/lib/rubocop.rb +14 -6
  83. metadata +8 -3
@@ -72,27 +72,27 @@ module RuboCop
72
72
  #
73
73
  # @example gives back a correctly qualified cop name
74
74
  #
75
- # cops = RuboCop::Cop::Cop.all
76
- # cops.
77
- # qualified_cop_name('Layout/EndOfLine') # => 'Layout/EndOfLine'
75
+ # registry = RuboCop::Cop::Registry
76
+ # registry.qualified_cop_name('Layout/EndOfLine', '') # => 'Layout/EndOfLine'
78
77
  #
79
78
  # @example fixes incorrect namespaces
80
79
  #
81
- # cops = RuboCop::Cop::Cop.all
82
- # cops.qualified_cop_name('Lint/EndOfLine') # => 'Layout/EndOfLine'
80
+ # registry = RuboCop::Cop::Registry
81
+ # registry.qualified_cop_name('Lint/EndOfLine', '') # => 'Layout/EndOfLine'
83
82
  #
84
83
  # @example namespaces bare cop identifiers
85
84
  #
86
- # cops = RuboCop::Cop::Cop.all
87
- # cops.qualified_cop_name('EndOfLine') # => 'Layout/EndOfLine'
85
+ # registry = RuboCop::Cop::Registry
86
+ # registry.qualified_cop_name('EndOfLine', '') # => 'Layout/EndOfLine'
88
87
  #
89
88
  # @example passes back unrecognized cop names
90
89
  #
91
- # cops = RuboCop::Cop::Cop.all
92
- # cops.qualified_cop_name('NotACop') # => 'NotACop'
90
+ # registry = RuboCop::Cop::Registry
91
+ # registry.qualified_cop_name('NotACop', '') # => 'NotACop'
93
92
  #
94
93
  # @param name [String] Cop name extracted from config
95
94
  # @param path [String, nil] Path of file that `name` was extracted from
95
+ # @param warn [Boolean] Print a warning if no department given for `name`
96
96
  #
97
97
  # @raise [AmbiguousCopName]
98
98
  # if a bare identifier with two possible namespaces is provided
@@ -158,7 +158,7 @@ module RuboCop
158
158
  end
159
159
 
160
160
  def enabled?(cop, config)
161
- return true if options.fetch(:only, []).include?(cop.cop_name)
161
+ return true if options[:only]&.include?(cop.cop_name)
162
162
 
163
163
  cfg = config.for_cop(cop)
164
164
 
@@ -182,8 +182,12 @@ module RuboCop
182
182
  cops.map(&:cop_name)
183
183
  end
184
184
 
185
+ def cops_for_department(department)
186
+ cops.select { |cop| cop.department == department.to_sym }
187
+ end
188
+
185
189
  def names_for_department(department)
186
- cops.select { |cop| cop.department == department.to_sym }.map(&:cop_name)
190
+ cops_for_department(department).map(&:cop_name)
187
191
  end
188
192
 
189
193
  def ==(other)
@@ -211,6 +215,14 @@ module RuboCop
211
215
  to_h[cop_name].first
212
216
  end
213
217
 
218
+ # When a cop name is given returns a single-element array with the cop class.
219
+ # When a department name is given returns an array with all the cop classes
220
+ # for that department.
221
+ def find_cops_by_directive(directive)
222
+ cop = find_by_cop_name(directive)
223
+ cop ? [cop] : cops_for_department(directive)
224
+ end
225
+
214
226
  def freeze
215
227
  clear_enrollment_queue
216
228
  unqualified_cop_names # build cache
@@ -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
@@ -0,0 +1,111 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Style
6
+ # In Ruby 3.1, `Array#intersect?` has been added.
7
+ #
8
+ # This cop identifies places where `(array1 & array2).any?`
9
+ # can be replaced by `array1.intersect?(array2)`.
10
+ #
11
+ # The `array1.intersect?(array2)` method is faster than
12
+ # `(array1 & array2).any?` and is more readable.
13
+ #
14
+ # @safety
15
+ # This cop cannot guarantee that array1 and array2 are
16
+ # actually arrays while method `intersect?` is for arrays only.
17
+ #
18
+ # @example
19
+ # # bad
20
+ # (array1 & array2).any?
21
+ # (array1 & array2).empty?
22
+ #
23
+ # # good
24
+ # array1.intersect?(array2)
25
+ # !array1.intersect?(array2)
26
+ #
27
+ # @example AllCops:ActiveSupportExtensionsEnabled: false (default)
28
+ # # good
29
+ # (array1 & array2).present?
30
+ # (array1 & array2).blank?
31
+ #
32
+ # @example AllCops:ActiveSupportExtensionsEnabled: true
33
+ # # bad
34
+ # (array1 & array2).present?
35
+ # (array1 & array2).blank?
36
+ #
37
+ # # good
38
+ # array1.intersect?(array2)
39
+ # !array1.intersect?(array2)
40
+ class ArrayIntersect < Base
41
+ extend AutoCorrector
42
+ extend TargetRubyVersion
43
+
44
+ minimum_target_ruby_version 3.1
45
+
46
+ # @!method regular_bad_intersection_check?(node)
47
+ def_node_matcher :regular_bad_intersection_check?, <<~PATTERN
48
+ (send
49
+ (begin
50
+ (send $(...) :& $(...))
51
+ ) ${:any? :empty?}
52
+ )
53
+ PATTERN
54
+
55
+ # @!method active_support_bad_intersection_check?(node)
56
+ def_node_matcher :active_support_bad_intersection_check?, <<~PATTERN
57
+ (send
58
+ (begin
59
+ (send $(...) :& $(...))
60
+ ) ${:present? :any? :blank? :empty?}
61
+ )
62
+ PATTERN
63
+
64
+ MSG = 'Use `%<negated>s%<receiver>s.intersect?(%<argument>s)` ' \
65
+ 'instead of `(%<receiver>s & %<argument>s).%<method_name>s`.'
66
+ STRAIGHT_METHODS = %i[present? any?].freeze
67
+ NEGATED_METHODS = %i[blank? empty?].freeze
68
+ RESTRICT_ON_SEND = (STRAIGHT_METHODS + NEGATED_METHODS).freeze
69
+
70
+ def on_send(node)
71
+ return unless (receiver, argument, method_name = bad_intersection_check?(node))
72
+
73
+ message = message(receiver.source, argument.source, method_name)
74
+
75
+ add_offense(node, message: message) do |corrector|
76
+ if straight?(method_name)
77
+ corrector.replace(node, "#{receiver.source}.intersect?(#{argument.source})")
78
+ else
79
+ corrector.replace(node, "!#{receiver.source}.intersect?(#{argument.source})")
80
+ end
81
+ end
82
+ end
83
+
84
+ private
85
+
86
+ def bad_intersection_check?(node)
87
+ if active_support_extensions_enabled?
88
+ active_support_bad_intersection_check?(node)
89
+ else
90
+ regular_bad_intersection_check?(node)
91
+ end
92
+ end
93
+
94
+ def straight?(method_name)
95
+ STRAIGHT_METHODS.include?(method_name.to_sym)
96
+ end
97
+
98
+ def message(receiver, argument, method_name)
99
+ negated = straight?(method_name) ? '' : '!'
100
+ format(
101
+ MSG,
102
+ negated: negated,
103
+ receiver: receiver,
104
+ argument: argument,
105
+ method_name: method_name
106
+ )
107
+ end
108
+ end
109
+ end
110
+ end
111
+ end
@@ -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)
@@ -112,7 +112,7 @@ module RuboCop
112
112
  end
113
113
 
114
114
  def range(begin_pos_node, end_pos_node)
115
- 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)
116
116
  end
117
117
  end
118
118
  end
@@ -94,6 +94,7 @@ module RuboCop
94
94
  #
95
95
  class GuardClause < Base
96
96
  extend AutoCorrector
97
+ include RangeHelp
97
98
  include MinBodyLength
98
99
  include StatementModifier
99
100
 
@@ -179,13 +180,35 @@ module RuboCop
179
180
  end
180
181
  end
181
182
 
183
+ # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
182
184
  def autocorrect(corrector, node, condition, replacement, guard)
183
185
  corrector.replace(node.loc.keyword.join(condition.loc.expression), replacement)
184
- corrector.remove(node.loc.end)
185
- return unless node.else?
186
186
 
187
- corrector.remove(node.loc.else)
188
- corrector.remove(branch_to_remove(node, guard))
187
+ if_branch = node.if_branch
188
+ else_branch = node.else_branch
189
+
190
+ if if_branch&.send_type? && if_branch.last_argument&.heredoc?
191
+ autocorrect_heredoc_argument(corrector, node, if_branch, else_branch, guard)
192
+ elsif else_branch&.send_type? && else_branch.last_argument&.heredoc?
193
+ autocorrect_heredoc_argument(corrector, node, else_branch, if_branch, guard)
194
+ else
195
+ corrector.remove(node.loc.end)
196
+ return unless node.else?
197
+
198
+ corrector.remove(node.loc.else)
199
+ corrector.remove(branch_to_remove(node, guard))
200
+ end
201
+ end
202
+ # rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
203
+
204
+ def autocorrect_heredoc_argument(corrector, node, heredoc_branch, leave_branch, guard)
205
+ remove_whole_lines(corrector, leave_branch.source_range)
206
+ remove_whole_lines(corrector, node.loc.else)
207
+ remove_whole_lines(corrector, node.loc.end)
208
+ remove_whole_lines(corrector, branch_to_remove(node, guard).source_range)
209
+ corrector.insert_after(
210
+ heredoc_branch.last_argument.loc.heredoc_end, "\n#{leave_branch.source}"
211
+ )
189
212
  end
190
213
 
191
214
  def branch_to_remove(node, guard)
@@ -222,7 +245,7 @@ module RuboCop
222
245
  end
223
246
 
224
247
  def accepted_if?(node, ending)
225
- return true if node.modifier_form? || node.ternary?
248
+ return true if node.modifier_form? || node.ternary? || node.elsif_conditional?
226
249
 
227
250
  if ending
228
251
  node.else?
@@ -231,6 +254,10 @@ module RuboCop
231
254
  end
232
255
  end
233
256
 
257
+ def remove_whole_lines(corrector, range)
258
+ corrector.remove(range_by_whole_lines(range, include_final_newline: true))
259
+ end
260
+
234
261
  def allowed_consecutive_conditionals?
235
262
  cop_config.fetch('AllowConsecutiveConditionals', false)
236
263
  end
@@ -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
@@ -39,9 +39,10 @@ module RuboCop
39
39
  def autocorrect(node)
40
40
  return correct_elsif(node) if node.else_branch.if_type?
41
41
 
42
+ then_code = node.if_branch ? node.if_branch.source : 'nil'
42
43
  else_code = node.else_branch ? node.else_branch.source : 'nil'
43
44
 
44
- "#{node.condition.source} ? #{node.if_branch.source} : #{else_code}"
45
+ "#{node.condition.source} ? #{then_code} : #{else_code}"
45
46
  end
46
47
 
47
48
  def correct_elsif(node)
@@ -44,7 +44,7 @@ module RuboCop
44
44
  PATTERN
45
45
 
46
46
  def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler
47
- return unless node.lambda? || node.proc?
47
+ return unless node.lambda_or_proc?
48
48
  return unless nil_return?(node.body)
49
49
 
50
50
  message = format(MSG, type: node.lambda? ? 'lambda' : 'proc')
@@ -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
@@ -13,6 +13,7 @@ module RuboCop
13
13
  # ----
14
14
  # Methods:
15
15
  # join: ''
16
+ # sum: 0
16
17
  # split: ' '
17
18
  # chomp: "\n"
18
19
  # chomp!: "\n"
@@ -33,6 +34,7 @@ module RuboCop
33
34
  # # bad
34
35
  # array.join('')
35
36
  # [1, 2, 3].join("")
37
+ # array.sum(0)
36
38
  # string.split(" ")
37
39
  # "first\nsecond".split(" ")
38
40
  # string.chomp("\n")
@@ -42,6 +44,7 @@ module RuboCop
42
44
  # # good
43
45
  # array.join
44
46
  # [1, 2, 3].join
47
+ # array.sum
45
48
  # string.split
46
49
  # "first second".split
47
50
  # string.chomp
@@ -0,0 +1,72 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Style
6
+ # Avoid redundant `::` prefix on constant.
7
+ #
8
+ # How Ruby searches constant is a bit complicated, and it can often be difficult to
9
+ # understand from the code whether the `::` is intended or not. Where `Module.nesting`
10
+ # is empty, there is no need to prepend `::`, so it would be nice to consistently
11
+ # avoid such meaningless `::` prefix to avoid confusion.
12
+ #
13
+ # @example
14
+ # # bad
15
+ # ::Const
16
+ #
17
+ # # good
18
+ # Const
19
+ #
20
+ # # bad
21
+ # class << self
22
+ # ::Const
23
+ # end
24
+ #
25
+ # # good
26
+ # class << self
27
+ # Const
28
+ # end
29
+ #
30
+ # # good
31
+ # class A
32
+ # ::Const
33
+ # end
34
+ #
35
+ # # good
36
+ # module A
37
+ # ::Const
38
+ # end
39
+ class RedundantConstantBase < Base
40
+ extend AutoCorrector
41
+
42
+ MSG = 'Remove redundant `::`.'
43
+
44
+ def on_cbase(node)
45
+ return unless bad?(node)
46
+
47
+ add_offense(node) do |corrector|
48
+ corrector.remove(node)
49
+ end
50
+ end
51
+
52
+ private
53
+
54
+ def bad?(node)
55
+ module_nesting_ancestors_of(node).none?
56
+ end
57
+
58
+ def module_nesting_ancestors_of(node)
59
+ node.each_ancestor(:class, :module).reject do |ancestor|
60
+ ancestor.class_type? && used_in_super_class_part?(node, class_node: ancestor)
61
+ end
62
+ end
63
+
64
+ def used_in_super_class_part?(node, class_node:)
65
+ class_node.parent_class&.each_descendant(:cbase)&.any? do |descendant|
66
+ descendant.equal?(node)
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
@@ -7,7 +7,7 @@ module RuboCop
7
7
  #
8
8
  # @safety
9
9
  # This cop is unsafe, as it can produce false positives if the receiver
10
- # is not an `Enumerable`.
10
+ # is not an `Enumerator`.
11
11
  #
12
12
  # @example
13
13
  #
@@ -61,19 +61,24 @@ module RuboCop
61
61
 
62
62
  # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
63
63
  def redundant_each_method(node)
64
- if node.method?(:each) && !node.parent.block_type?
64
+ return if node.last_argument&.block_pass_type?
65
+
66
+ if node.method?(:each) && !node.parent&.block_type?
65
67
  ancestor_node = node.each_ancestor(:send).detect do |ancestor|
66
- RESTRICT_ON_SEND.include?(ancestor.method_name) || ancestor.method?(:reverse_each)
68
+ ancestor.receiver == node &&
69
+ (RESTRICT_ON_SEND.include?(ancestor.method_name) || ancestor.method?(:reverse_each))
67
70
  end
71
+
72
+ return ancestor_node if ancestor_node
68
73
  end
69
74
 
70
- ancestor_node || node.each_descendant(:send).detect do |descendant|
71
- next if descendant.parent.block_type?
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?
72
78
 
73
- detected = descendant.method_name.to_s.start_with?('each_') unless node.method?(:each)
79
+ detected = prev_method.method_name.to_s.start_with?('each_') unless node.method?(:each)
74
80
 
75
- detected || descendant.method?(:reverse_each)
76
- end
81
+ prev_method if detected || prev_method.method?(:reverse_each)
77
82
  end
78
83
  # rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
79
84
 
@@ -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
 
@@ -53,6 +53,13 @@ module RuboCop
53
53
 
54
54
  MSG = 'Redundant `return` detected.'
55
55
  MULTI_RETURN_MSG = 'To return multiple values, use an array.'
56
+ RESTRICT_ON_SEND = %i[define_method define_singleton_method].freeze
57
+
58
+ def on_send(node)
59
+ return unless (parent = node.parent) && parent.block_type?
60
+
61
+ check_branch(parent.body)
62
+ end
56
63
 
57
64
  def on_def(node)
58
65
  check_branch(node.body)
@@ -184,7 +184,7 @@ module RuboCop
184
184
  end
185
185
 
186
186
  def arg_value(node)
187
- arg_node(node).nil? ? nil : arg_node(node).node_parts.first
187
+ arg_node(node)&.node_parts&.first
188
188
  end
189
189
 
190
190
  # This gets the start of the accessor whether it has a dot