rubocop 1.6.1 → 1.7.0

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