rubocop 1.6.1 → 1.7.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 (55) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  3. data/config/default.yml +48 -6
  4. data/lib/rubocop.rb +4 -0
  5. data/lib/rubocop/config.rb +8 -5
  6. data/lib/rubocop/config_loader.rb +10 -6
  7. data/lib/rubocop/config_loader_resolver.rb +21 -4
  8. data/lib/rubocop/config_obsoletion.rb +5 -3
  9. data/lib/rubocop/cop/gemspec/required_ruby_version.rb +3 -2
  10. data/lib/rubocop/cop/internal_affairs.rb +1 -0
  11. data/lib/rubocop/cop/internal_affairs/style_detected_api_use.rb +145 -0
  12. data/lib/rubocop/cop/layout/empty_line_between_defs.rb +19 -3
  13. data/lib/rubocop/cop/layout/heredoc_argument_closing_parenthesis.rb +14 -0
  14. data/lib/rubocop/cop/layout/rescue_ensure_alignment.rb +3 -10
  15. data/lib/rubocop/cop/layout/space_around_equals_in_parameter_default.rb +1 -0
  16. data/lib/rubocop/cop/layout/space_before_block_braces.rb +2 -0
  17. data/lib/rubocop/cop/layout/space_before_brackets.rb +64 -0
  18. data/lib/rubocop/cop/layout/space_inside_block_braces.rb +13 -10
  19. data/lib/rubocop/cop/layout/space_inside_hash_literal_braces.rb +2 -2
  20. data/lib/rubocop/cop/lint/ambiguous_assignment.rb +59 -0
  21. data/lib/rubocop/cop/lint/binary_operator_with_identical_operands.rb +7 -2
  22. data/lib/rubocop/cop/lint/duplicate_branch.rb +64 -2
  23. data/lib/rubocop/cop/lint/redundant_splat_expansion.rb +50 -17
  24. data/lib/rubocop/cop/lint/shadowed_exception.rb +1 -11
  25. data/lib/rubocop/cop/lint/unreachable_loop.rb +17 -0
  26. data/lib/rubocop/cop/metrics/utils/code_length_calculator.rb +1 -1
  27. data/lib/rubocop/cop/naming/memoized_instance_variable_name.rb +59 -5
  28. data/lib/rubocop/cop/registry.rb +10 -0
  29. data/lib/rubocop/cop/style/access_modifier_declarations.rb +3 -1
  30. data/lib/rubocop/cop/style/collection_methods.rb +14 -1
  31. data/lib/rubocop/cop/style/commented_keyword.rb +22 -5
  32. data/lib/rubocop/cop/style/for.rb +2 -0
  33. data/lib/rubocop/cop/style/hash_except.rb +95 -0
  34. data/lib/rubocop/cop/style/keyword_parameters_order.rb +12 -2
  35. data/lib/rubocop/cop/style/lambda_call.rb +2 -1
  36. data/lib/rubocop/cop/style/method_call_with_args_parentheses.rb +3 -0
  37. data/lib/rubocop/cop/style/method_call_with_args_parentheses/omit_parentheses.rb +16 -6
  38. data/lib/rubocop/cop/style/method_def_parentheses.rb +7 -0
  39. data/lib/rubocop/cop/style/multiline_method_signature.rb +26 -1
  40. data/lib/rubocop/cop/style/multiline_when_then.rb +3 -1
  41. data/lib/rubocop/cop/style/mutable_constant.rb +13 -3
  42. data/lib/rubocop/cop/style/raise_args.rb +2 -0
  43. data/lib/rubocop/cop/style/redundant_argument.rb +7 -1
  44. data/lib/rubocop/cop/style/redundant_freeze.rb +8 -4
  45. data/lib/rubocop/cop/style/single_line_methods.rb +4 -0
  46. data/lib/rubocop/cop/style/symbol_proc.rb +5 -4
  47. data/lib/rubocop/cop/util.rb +3 -1
  48. data/lib/rubocop/options.rb +9 -9
  49. data/lib/rubocop/rspec/cop_helper.rb +0 -4
  50. data/lib/rubocop/rspec/expect_offense.rb +34 -22
  51. data/lib/rubocop/runner.rb +16 -1
  52. data/lib/rubocop/target_finder.rb +4 -2
  53. data/lib/rubocop/util.rb +16 -0
  54. data/lib/rubocop/version.rb +8 -2
  55. metadata +8 -3
@@ -106,23 +106,13 @@ module RuboCop
106
106
  error && error.ancestors[1] == SystemCallError
107
107
  end
108
108
 
109
- def silence_warnings
110
- # Replaces Kernel::silence_warnings since it hides any warnings,
111
- # including the RuboCop ones
112
- old_verbose = $VERBOSE
113
- $VERBOSE = nil
114
- yield
115
- ensure
116
- $VERBOSE = old_verbose
117
- end
118
-
119
109
  def evaluate_exceptions(group)
120
110
  rescued_exceptions = group.exceptions
121
111
 
122
112
  if rescued_exceptions.any?
123
113
  rescued_exceptions.each_with_object([]) do |exception, converted|
124
114
  begin
125
- silence_warnings do
115
+ RuboCop::Util.silence_warnings do
126
116
  # Avoid printing deprecation warnings about constants
127
117
  converted << Kernel.const_get(exception.source)
128
118
  end
@@ -9,6 +9,12 @@ module RuboCop
9
9
  # In rare cases where only one iteration (or at most one iteration) is intended behavior,
10
10
  # the code should be refactored to use `if` conditionals.
11
11
  #
12
+ # NOTE: Block methods that are used with `Enumerable`s are considered to be loops.
13
+ #
14
+ # `IgnoredPatterns` can be used to match against the block receiver in order to allow
15
+ # code that would otherwise be registered as an offense (eg. `times` used not in an
16
+ # `Enumerable` context).
17
+ #
12
18
  # @example
13
19
  # # bad
14
20
  # while node
@@ -70,7 +76,16 @@ module RuboCop
70
76
  # raise NotFoundError
71
77
  # end
72
78
  #
79
+ # # bad
80
+ # 2.times { raise ArgumentError }
81
+ #
82
+ # @example IgnoredPatterns: [/(exactly|at_least|at_most)\(\d+\)\.times/] (default)
83
+ #
84
+ # # good
85
+ # exactly(2).times { raise StandardError }
73
86
  class UnreachableLoop < Base
87
+ include IgnoredPattern
88
+
74
89
  MSG = 'This loop will have at most one iteration.'
75
90
 
76
91
  def on_while(node)
@@ -91,6 +106,8 @@ module RuboCop
91
106
  return false unless node.block_type?
92
107
 
93
108
  send_node = node.send_node
109
+ return false if matches_ignored_pattern?(send_node.source)
110
+
94
111
  send_node.enumerable_method? || send_node.enumerator_method? || send_node.method?(:loop)
95
112
  end
96
113
 
@@ -124,7 +124,7 @@ module RuboCop
124
124
  end
125
125
 
126
126
  def classlike_node?(node)
127
- CLASSLIKE_TYPES.include?(node.type)
127
+ CLASSLIKE_TYPES.include?(node&.type)
128
128
  end
129
129
 
130
130
  def foldable_node?(node)
@@ -4,7 +4,9 @@ module RuboCop
4
4
  module Cop
5
5
  module Naming
6
6
  # This cop checks for memoized methods whose instance variable name
7
- # does not match the method name.
7
+ # does not match the method name. Applies to both regular methods
8
+ # (defined with `def`) and dynamic methods (defined with
9
+ # `define_method` or `define_singleton_method`).
8
10
  #
9
11
  # This cop can be configured with the EnforcedStyleForLeadingUnderscores
10
12
  # directive. It can be configured to allow for memoized instance variables
@@ -48,6 +50,17 @@ module RuboCop
48
50
  # @foo ||= calculate_expensive_thing(helper_variable)
49
51
  # end
50
52
  #
53
+ # # good
54
+ # define_method(:foo) do
55
+ # @foo ||= calculate_expensive_thing
56
+ # end
57
+ #
58
+ # # good
59
+ # define_method(:foo) do
60
+ # return @foo if defined?(@foo)
61
+ # @foo = calculate_expensive_thing
62
+ # end
63
+ #
51
64
  # @example EnforcedStyleForLeadingUnderscores: required
52
65
  # # bad
53
66
  # def foo
@@ -79,6 +92,17 @@ module RuboCop
79
92
  # @_foo = calculate_expensive_thing
80
93
  # end
81
94
  #
95
+ # # good
96
+ # define_method(:foo) do
97
+ # @_foo ||= calculate_expensive_thing
98
+ # end
99
+ #
100
+ # # good
101
+ # define_method(:foo) do
102
+ # return @_foo if defined?(@_foo)
103
+ # @_foo = calculate_expensive_thing
104
+ # end
105
+ #
82
106
  # @example EnforcedStyleForLeadingUnderscores :optional
83
107
  # # bad
84
108
  # def foo
@@ -105,6 +129,16 @@ module RuboCop
105
129
  # return @_foo if defined?(@_foo)
106
130
  # @_foo = calculate_expensive_thing
107
131
  # end
132
+ #
133
+ # # good
134
+ # define_method(:foo) do
135
+ # @foo ||= calculate_expensive_thing
136
+ # end
137
+ #
138
+ # # good
139
+ # define_method(:foo) do
140
+ # @_foo ||= calculate_expensive_thing
141
+ # end
108
142
  class MemoizedInstanceVariableName < Base
109
143
  include ConfigurableEnforcedStyle
110
144
 
@@ -112,17 +146,27 @@ module RuboCop
112
146
  'method name `%<method>s`. Use `@%<suggested_var>s` instead.'
113
147
  UNDERSCORE_REQUIRED = 'Memoized variable `%<var>s` does not start ' \
114
148
  'with `_`. Use `@%<suggested_var>s` instead.'
149
+ DYNAMIC_DEFINE_METHODS = %i[define_method define_singleton_method].to_set.freeze
150
+
151
+ def_node_matcher :method_definition?, <<~PATTERN
152
+ ${
153
+ (block (send _ %DYNAMIC_DEFINE_METHODS ({sym str} $_)) ...)
154
+ (def $_ ...)
155
+ (defs _ $_ ...)
156
+ }
157
+ PATTERN
115
158
 
116
159
  # rubocop:disable Metrics/AbcSize
117
160
  def on_or_asgn(node)
118
161
  lhs, _value = *node
119
162
  return unless lhs.ivasgn_type?
120
- return unless (method_node = node.each_ancestor(:def, :defs).first)
163
+
164
+ method_node, method_name = find_definition(node)
165
+ return unless method_node
121
166
 
122
167
  body = method_node.body
123
168
  return unless body == node || body.children.last == node
124
169
 
125
- method_name = method_node.method_name
126
170
  return if matches?(method_name, lhs)
127
171
 
128
172
  msg = format(
@@ -147,11 +191,10 @@ module RuboCop
147
191
  arg = node.arguments.first
148
192
  return unless arg.ivar_type?
149
193
 
150
- method_node = node.each_ancestor(:def, :defs).first
194
+ method_node, method_name = find_definition(node)
151
195
  return unless method_node
152
196
 
153
197
  var_name = arg.children.first
154
- method_name = method_node.method_name
155
198
  defined_memoized?(method_node.body, var_name) do |defined_ivar, return_ivar, ivar_assign|
156
199
  return if matches?(method_name, ivar_assign)
157
200
 
@@ -174,6 +217,17 @@ module RuboCop
174
217
  'EnforcedStyleForLeadingUnderscores'
175
218
  end
176
219
 
220
+ def find_definition(node)
221
+ # Methods can be defined in a `def` or `defs`,
222
+ # or dynamically via a `block` node.
223
+ node.each_ancestor(:def, :defs, :block).each do |ancestor|
224
+ method_node, method_name = method_definition?(ancestor)
225
+ return [method_node, method_name] if method_node
226
+ end
227
+
228
+ nil
229
+ end
230
+
177
231
  def matches?(method_name, ivar_assign)
178
232
  return true if ivar_assign.nil? || method_name == :initialize
179
233
 
@@ -204,6 +204,12 @@ module RuboCop
204
204
  to_h[cop_name].first
205
205
  end
206
206
 
207
+ def freeze
208
+ clear_enrollment_queue
209
+ unqualified_cop_names # build cache
210
+ super
211
+ end
212
+
207
213
  @global = new
208
214
 
209
215
  class << self
@@ -228,6 +234,10 @@ module RuboCop
228
234
  @global = previous
229
235
  end
230
236
 
237
+ def self.reset!
238
+ @global = new
239
+ end
240
+
231
241
  private
232
242
 
233
243
  def initialize_copy(reg)
@@ -87,7 +87,9 @@ module RuboCop
87
87
  return if allow_modifiers_on_symbols?(node)
88
88
 
89
89
  if offense?(node)
90
- add_offense(node.loc.selector) if opposite_style_detected
90
+ add_offense(node.loc.selector) do
91
+ opposite_style_detected
92
+ end
91
93
  else
92
94
  correct_style_detected
93
95
  end
@@ -48,7 +48,7 @@ module RuboCop
48
48
  end
49
49
 
50
50
  def on_send(node)
51
- return unless node.arguments.one? && node.first_argument.block_pass_type?
51
+ return unless implicit_block?(node)
52
52
 
53
53
  check_method_node(node)
54
54
  end
@@ -64,9 +64,22 @@ module RuboCop
64
64
  end
65
65
  end
66
66
 
67
+ def implicit_block?(node)
68
+ return false unless node.arguments.any?
69
+
70
+ node.last_argument.block_pass_type? ||
71
+ node.last_argument.sym_type? && methods_accepting_symbol.include?(node.method_name.to_s)
72
+ end
73
+
67
74
  def message(node)
68
75
  format(MSG, prefer: preferred_method(node.method_name), current: node.method_name)
69
76
  end
77
+
78
+ # Some enumerable methods accept a bare symbol (ie. _not_ Symbol#to_proc) instead
79
+ # of a block.
80
+ def methods_accepting_symbol
81
+ Array(cop_config['MethodsAcceptingSymbol'])
82
+ end
70
83
  end
71
84
  end
72
85
  end
@@ -4,12 +4,15 @@ module RuboCop
4
4
  module Cop
5
5
  module Style
6
6
  # This cop checks for comments put on the same line as some keywords.
7
- # These keywords are: `begin`, `class`, `def`, `end`, `module`.
7
+ # These keywords are: `class`, `module`, `def`, `begin`, `end`.
8
8
  #
9
9
  # Note that some comments
10
10
  # (`:nodoc:`, `:yields:`, `rubocop:disable` and `rubocop:todo`)
11
11
  # are allowed.
12
12
  #
13
+ # Auto-correction removes comments from `end` keyword and keeps comments
14
+ # for `class`, `module`, `def` and `begin` above the keyword.
15
+ #
13
16
  # @example
14
17
  # # bad
15
18
  # if condition
@@ -34,16 +37,17 @@ module RuboCop
34
37
  # y
35
38
  # end
36
39
  class CommentedKeyword < Base
40
+ include RangeHelp
41
+ extend AutoCorrector
42
+
37
43
  MSG = 'Do not place comments on the same line as the ' \
38
44
  '`%<keyword>s` keyword.'
39
45
 
40
46
  def on_new_investigation
41
47
  processed_source.comments.each do |comment|
42
- next unless (match = line(comment).match(/(?<keyword>\S+).*#/))
48
+ next unless (match = line(comment).match(/(?<keyword>\S+).*#/)) && offensive?(comment)
43
49
 
44
- if offensive?(comment)
45
- add_offense(comment, message: format(MSG, keyword: match[:keyword]))
46
- end
50
+ register_offense(comment, match[:keyword])
47
51
  end
48
52
  end
49
53
 
@@ -60,6 +64,19 @@ module RuboCop
60
64
  ].freeze
61
65
  ALLOWED_COMMENT_REGEXES = ALLOWED_COMMENTS.map { |c| /#\s*#{c}/ }.freeze
62
66
 
67
+ def register_offense(comment, matched_keyword)
68
+ add_offense(comment, message: format(MSG, keyword: matched_keyword)) do |corrector|
69
+ range = range_with_surrounding_space(range: comment.loc.expression, newlines: false)
70
+ corrector.remove(range)
71
+
72
+ unless matched_keyword == 'end'
73
+ corrector.insert_before(
74
+ range.source_buffer.line_range(comment.loc.line), "#{comment.text}\n"
75
+ )
76
+ end
77
+ end
78
+ end
79
+
63
80
  def offensive?(comment)
64
81
  line = line(comment)
65
82
  KEYWORD_REGEXES.any? { |r| r.match?(line) } &&
@@ -51,6 +51,7 @@ module RuboCop
51
51
  if style == :each
52
52
  add_offense(node, message: PREFER_EACH) do |corrector|
53
53
  ForToEachCorrector.new(node).call(corrector)
54
+ opposite_style_detected
54
55
  end
55
56
  else
56
57
  correct_style_detected
@@ -63,6 +64,7 @@ module RuboCop
63
64
  if style == :for
64
65
  add_offense(node, message: PREFER_FOR) do |corrector|
65
66
  EachToForCorrector.new(node).call(corrector)
67
+ opposite_style_detected
66
68
  end
67
69
  else
68
70
  correct_style_detected
@@ -0,0 +1,95 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Style
6
+ # This cop checks for usages of `Hash#reject`, `Hash#select`, and `Hash#filter` methods
7
+ # that can be replaced with `Hash#except` method.
8
+ #
9
+ # This cop should only be enabled on Ruby version 3.0 or higher.
10
+ # (`Hash#except` was added in Ruby 3.0.)
11
+ #
12
+ # For safe detection, it is limited to commonly used string and symbol comparisons
13
+ # when used `==`.
14
+ # And do not check `Hash#delete_if` and `Hash#keep_if` to change receiver object.
15
+ #
16
+ # @example
17
+ #
18
+ # # bad
19
+ # {foo: 1, bar: 2, baz: 3}.reject {|k, v| k == :bar }
20
+ # {foo: 1, bar: 2, baz: 3}.select {|k, v| k != :bar }
21
+ # {foo: 1, bar: 2, baz: 3}.filter {|k, v| k != :bar }
22
+ #
23
+ # # good
24
+ # {foo: 1, bar: 2, baz: 3}.except(:bar)
25
+ #
26
+ class HashExcept < Base
27
+ include RangeHelp
28
+ extend TargetRubyVersion
29
+ extend AutoCorrector
30
+
31
+ minimum_target_ruby_version 3.0
32
+
33
+ MSG = 'Use `%<prefer>s` instead.'
34
+ RESTRICT_ON_SEND = %i[reject select filter].freeze
35
+
36
+ def_node_matcher :bad_method?, <<~PATTERN
37
+ (block
38
+ (send _ _)
39
+ (args
40
+ (arg _)
41
+ (arg _))
42
+ (send
43
+ _ {:== :!= :eql?} _))
44
+ PATTERN
45
+
46
+ def on_send(node)
47
+ block = node.parent
48
+ return unless bad_method?(block) && semantically_except_method?(node, block)
49
+
50
+ except_key = except_key(block)
51
+ return unless safe_to_register_offense?(block, except_key)
52
+
53
+ range = offense_range(node)
54
+ preferred_method = "except(#{except_key.source})"
55
+
56
+ add_offense(range, message: format(MSG, prefer: preferred_method)) do |corrector|
57
+ corrector.replace(range, preferred_method)
58
+ end
59
+ end
60
+
61
+ private
62
+
63
+ def semantically_except_method?(send, block)
64
+ body = block.body
65
+
66
+ case send.method_name
67
+ when :reject
68
+ body.method?('==') || body.method?('eql?')
69
+ when :select, :filter
70
+ body.method?('!=')
71
+ else
72
+ false
73
+ end
74
+ end
75
+
76
+ def safe_to_register_offense?(block, except_key)
77
+ return true if block.body.method?('eql?')
78
+
79
+ except_key.sym_type? || except_key.str_type?
80
+ end
81
+
82
+ def except_key(node)
83
+ key_argument = node.argument_list.first
84
+ lhs, _method_name, rhs = *node.body
85
+
86
+ [lhs, rhs].find { |operand| operand.source != key_argument.source }
87
+ end
88
+
89
+ def offense_range(node)
90
+ range_between(node.loc.selector.begin_pos, node.parent.loc.end.end_pos)
91
+ end
92
+ end
93
+ end
94
+ end
95
+ end
@@ -21,6 +21,16 @@ module RuboCop
21
21
  # # body omitted
22
22
  # end
23
23
  #
24
+ # # bad
25
+ # do_something do |first: false, second:, third: 10|
26
+ # # body omitted
27
+ # end
28
+ #
29
+ # # good
30
+ # do_something do |second:, first: false, third: 10|
31
+ # # body omitted
32
+ # end
33
+ #
24
34
  class KeywordParametersOrder < Base
25
35
  include RangeHelp
26
36
  extend AutoCorrector
@@ -35,7 +45,7 @@ module RuboCop
35
45
  if node.parent.find(&:kwoptarg_type?) == node
36
46
  corrector.insert_before(node, "#{kwarg_nodes.map(&:source).join(', ')}, ")
37
47
 
38
- arguments = node.each_ancestor(:def, :defs).first.arguments
48
+ arguments = node.each_ancestor(:def, :defs, :block).first.arguments
39
49
  append_newline_to_last_kwoptarg(arguments, corrector) unless parentheses?(arguments)
40
50
 
41
51
  remove_kwargs(kwarg_nodes, corrector)
@@ -50,7 +60,7 @@ module RuboCop
50
60
  return if last_argument.kwrestarg_type? || last_argument.blockarg_type?
51
61
 
52
62
  last_kwoptarg = arguments.reverse.find(&:kwoptarg_type?)
53
- corrector.insert_after(last_kwoptarg, "\n")
63
+ corrector.insert_after(last_kwoptarg, "\n") unless arguments.parent.block_type?
54
64
  end
55
65
 
56
66
  def remove_kwargs(kwarg_nodes, corrector)