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
@@ -27,8 +27,9 @@ module RuboCop
27
27
  def on_send(node)
28
28
  return unless node.receiver
29
29
 
30
- if offense?(node) && opposite_style_detected
30
+ if offense?(node)
31
31
  add_offense(node) do |corrector|
32
+ opposite_style_detected
32
33
  autocorrect(corrector, node)
33
34
  end
34
35
  else
@@ -40,6 +40,9 @@ module RuboCop
40
40
  # to `true` allows the presence of parentheses in such a method call
41
41
  # even with arguments.
42
42
  #
43
+ # NOTE: Parens are required around a method with arguments when inside an
44
+ # endless method definition (>= Ruby 3.0).
45
+ #
43
46
  # @example EnforcedStyle: require_parentheses (default)
44
47
  #
45
48
  # # bad
@@ -14,25 +14,35 @@ module RuboCop
14
14
 
15
15
  def omit_parentheses(node)
16
16
  return unless node.parenthesized?
17
+ return if inside_endless_method_def?(node)
17
18
  return if node.implicit_call?
18
19
  return if super_call_without_arguments?(node)
19
20
  return if allowed_camel_case_method_call?(node)
20
21
  return if legitimate_call_with_parentheses?(node)
21
22
 
22
23
  add_offense(offense_range(node), message: OMIT_MSG) do |corrector|
23
- if parentheses_at_the_end_of_multiline_call?(node)
24
- corrector.replace(args_begin(node), ' \\')
25
- else
26
- corrector.replace(args_begin(node), ' ')
27
- end
28
- corrector.remove(node.loc.end)
24
+ auto_correct(corrector, node)
25
+ end
26
+ end
27
+
28
+ def auto_correct(corrector, node)
29
+ if parentheses_at_the_end_of_multiline_call?(node)
30
+ corrector.replace(args_begin(node), ' \\')
31
+ else
32
+ corrector.replace(args_begin(node), ' ')
29
33
  end
34
+ corrector.remove(node.loc.end)
30
35
  end
31
36
 
32
37
  def offense_range(node)
33
38
  node.loc.begin.join(node.loc.end)
34
39
  end
35
40
 
41
+ def inside_endless_method_def?(node)
42
+ # parens are required around arguments inside an endless method
43
+ node.each_ancestor(:def).any?(&:endless?) && node.arguments.any?
44
+ end
45
+
36
46
  def super_call_without_arguments?(node)
37
47
  node.super_type? && node.arguments.none?
38
48
  end
@@ -6,6 +6,9 @@ module RuboCop
6
6
  # This cop checks for parentheses around the arguments in method
7
7
  # definitions. Both instance and class/singleton methods are checked.
8
8
  #
9
+ # This cop does not consider endless methods, since parentheses are
10
+ # always required for them.
11
+ #
9
12
  # @example EnforcedStyle: require_parentheses (default)
10
13
  # # The `require_parentheses` style requires method definitions
11
14
  # # to always use parentheses
@@ -94,6 +97,8 @@ module RuboCop
94
97
  'parameters.'
95
98
 
96
99
  def on_def(node)
100
+ return if node.endless?
101
+
97
102
  args = node.arguments
98
103
 
99
104
  if require_parentheses?(args)
@@ -142,6 +147,7 @@ module RuboCop
142
147
 
143
148
  add_offense(location, message: MSG_MISSING) do |corrector|
144
149
  correct_definition(node, corrector)
150
+ unexpected_style_detected 'require_no_parentheses'
145
151
  end
146
152
  end
147
153
 
@@ -149,6 +155,7 @@ module RuboCop
149
155
  add_offense(args, message: MSG_PRESENT) do |corrector|
150
156
  # offense is registered on args node when parentheses are unwanted
151
157
  correct_arguments(args, corrector)
158
+ unexpected_style_detected 'require_parentheses'
152
159
  end
153
160
  end
154
161
  end
@@ -19,6 +19,9 @@ module RuboCop
19
19
  # end
20
20
  #
21
21
  class MultilineMethodSignature < Base
22
+ include RangeHelp
23
+ extend AutoCorrector
24
+
22
25
  MSG = 'Avoid multi-line method signatures.'
23
26
 
24
27
  def on_def(node)
@@ -26,12 +29,34 @@ module RuboCop
26
29
  return if opening_line(node) == closing_line(node)
27
30
  return if correction_exceeds_max_line_length?(node)
28
31
 
29
- add_offense(node)
32
+ add_offense(node) do |corrector|
33
+ autocorrect(corrector, node)
34
+ end
30
35
  end
31
36
  alias on_defs on_def
32
37
 
33
38
  private
34
39
 
40
+ def autocorrect(corrector, node)
41
+ arguments = node.arguments
42
+ joined_arguments = arguments.map(&:source).join(', ')
43
+ last_line_source_of_arguments = processed_source[arguments.last_line - 1].strip
44
+
45
+ if last_line_source_of_arguments.start_with?(')')
46
+ joined_arguments = "#{joined_arguments}#{last_line_source_of_arguments}"
47
+
48
+ corrector.remove(range_by_whole_lines(arguments.loc.end, include_final_newline: true))
49
+ end
50
+
51
+ corrector.replace(arguments_range(node), joined_arguments)
52
+ end
53
+
54
+ def arguments_range(node)
55
+ range_between(
56
+ node.first_argument.source_range.begin_pos, node.last_argument.source_range.end_pos
57
+ )
58
+ end
59
+
35
60
  def opening_line(node)
36
61
  node.first_line
37
62
  end
@@ -58,7 +58,9 @@ module RuboCop
58
58
  private
59
59
 
60
60
  def require_then?(when_node)
61
- return true if when_node.conditions.count >= 2
61
+ unless when_node.conditions.first.first_line == when_node.conditions.last.last_line
62
+ return true
63
+ end
62
64
  return false unless when_node.body
63
65
 
64
66
  when_node.loc.line == when_node.body.loc.line
@@ -14,6 +14,8 @@ module RuboCop
14
14
  # positives. Luckily, there is no harm in freezing an already
15
15
  # frozen object.
16
16
  #
17
+ # NOTE: Regexp and Range literals are frozen objects since Ruby 3.0.
18
+ #
17
19
  # @example EnforcedStyle: literals (default)
18
20
  # # bad
19
21
  # CONST = [1, 2, 3]
@@ -94,7 +96,8 @@ module RuboCop
94
96
  range_enclosed_in_parentheses = range_enclosed_in_parentheses?(value)
95
97
 
96
98
  return unless mutable_literal?(value) ||
97
- range_enclosed_in_parentheses
99
+ target_ruby_version <= 2.7 && range_enclosed_in_parentheses
100
+
98
101
  return if FROZEN_STRING_LITERAL_TYPES.include?(value.type) &&
99
102
  frozen_string_literals_enabled?
100
103
 
@@ -119,11 +122,14 @@ module RuboCop
119
122
  end
120
123
 
121
124
  def mutable_literal?(value)
122
- value&.mutable_literal?
125
+ return false if value.nil?
126
+ return false if frozen_regexp_or_range_literals?(value)
127
+
128
+ value.mutable_literal?
123
129
  end
124
130
 
125
131
  def immutable_literal?(node)
126
- node.nil? || node.immutable_literal?
132
+ node.nil? || frozen_regexp_or_range_literals?(node) || node.immutable_literal?
127
133
  end
128
134
 
129
135
  def frozen_string_literal?(node)
@@ -131,6 +137,10 @@ module RuboCop
131
137
  frozen_string_literals_enabled?
132
138
  end
133
139
 
140
+ def frozen_regexp_or_range_literals?(node)
141
+ target_ruby_version >= 3.0 && (node.regexp_type? || node.range_type?)
142
+ end
143
+
134
144
  def requires_parentheses?(node)
135
145
  node.range_type? ||
136
146
  (node.send_type? && node.loc.dot.nil?)
@@ -95,6 +95,7 @@ module RuboCop
95
95
  replacement = correction_exploded_to_compact(node)
96
96
 
97
97
  corrector.replace(node, replacement)
98
+ opposite_style_detected
98
99
  end
99
100
  else
100
101
  correct_style_detected
@@ -115,6 +116,7 @@ module RuboCop
115
116
  replacement = correction_compact_to_exploded(node)
116
117
 
117
118
  corrector.replace(node, replacement)
119
+ opposite_style_detected
118
120
  end
119
121
  end
120
122
 
@@ -12,7 +12,7 @@ module RuboCop
12
12
  # 1. This cop matches for method names only and hence cannot tell apart
13
13
  # methods with same name in different classes.
14
14
  # 2. This cop is limited to methods with single parameter.
15
- # 3. This cop is unsafe if certain special global variables (e.g. `$;`) are set.
15
+ # 3. This cop is unsafe if certain special global variables (e.g. `$;`, `$/`) are set.
16
16
  # That depends on the nature of the target methods, of course.
17
17
  #
18
18
  # Method names and their redundant arguments can be configured like this:
@@ -20,6 +20,8 @@ module RuboCop
20
20
  # Methods:
21
21
  # join: ''
22
22
  # split: ' '
23
+ # chomp: "\n"
24
+ # chomp!: "\n"
23
25
  # foo: 2
24
26
  #
25
27
  # @example
@@ -28,6 +30,8 @@ module RuboCop
28
30
  # [1, 2, 3].join("")
29
31
  # string.split(" ")
30
32
  # "first\nsecond".split(" ")
33
+ # string.chomp("\n")
34
+ # string.chomp!("\n")
31
35
  # A.foo(2)
32
36
  #
33
37
  # # good
@@ -35,6 +39,8 @@ module RuboCop
35
39
  # [1, 2, 3].join
36
40
  # string.split
37
41
  # "first second".split
42
+ # string.chomp
43
+ # string.chomp!
38
44
  # A.foo
39
45
  class RedundantArgument < Base
40
46
  include RangeHelp
@@ -3,7 +3,9 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Style
6
- # This cop check for uses of Object#freeze on immutable objects.
6
+ # This cop check for uses of `Object#freeze` on immutable objects.
7
+ #
8
+ # NOTE: Regexp and Range literals are frozen objects since Ruby 3.0.
7
9
  #
8
10
  # @example
9
11
  # # bad
@@ -37,8 +39,10 @@ module RuboCop
37
39
 
38
40
  return true if node.immutable_literal?
39
41
 
40
- FROZEN_STRING_LITERAL_TYPES.include?(node.type) &&
41
- frozen_string_literals_enabled?
42
+ return true if FROZEN_STRING_LITERAL_TYPES.include?(node.type) &&
43
+ frozen_string_literals_enabled?
44
+
45
+ target_ruby_version >= 3.0 && (node.regexp_type? || node.range_type?)
42
46
  end
43
47
 
44
48
  def strip_parenthesis(node)
@@ -52,7 +56,7 @@ module RuboCop
52
56
  def_node_matcher :operation_produces_immutable_object?, <<~PATTERN
53
57
  {
54
58
  (begin (send {float int} {:+ :- :* :** :/ :% :<<} _))
55
- (begin (send !(str _) {:+ :- :* :** :/ :%} {float int}))
59
+ (begin (send !{(str _) array} {:+ :- :* :** :/ :%} {float int}))
56
60
  (begin (send _ {:== :=== :!= :<= :>= :< :>} _))
57
61
  (send (const {nil? cbase} :ENV) :[] _)
58
62
  (send _ {:count :length :size} ...)
@@ -6,6 +6,8 @@ module RuboCop
6
6
  # This cop checks for single-line method definitions that contain a body.
7
7
  # It will accept single-line methods with no body.
8
8
  #
9
+ # Endless methods added in Ruby 3.0 are also accepted by this cop.
10
+ #
9
11
  # @example
10
12
  # # bad
11
13
  # def some_method; body end
@@ -15,6 +17,7 @@ module RuboCop
15
17
  # # good
16
18
  # def self.resource_class=(klass); end
17
19
  # def @table.columns; end
20
+ # def some_method() = body
18
21
  #
19
22
  # @example AllowIfMethodIsEmpty: true (default)
20
23
  # # good
@@ -32,6 +35,7 @@ module RuboCop
32
35
 
33
36
  def on_def(node)
34
37
  return unless node.single_line?
38
+ return if node.endless?
35
39
  return if allow_empty? && !node.body
36
40
 
37
41
  add_offense(node) do |corrector|
@@ -22,11 +22,12 @@ module RuboCop
22
22
  SUPER_TYPES = %i[super zsuper].freeze
23
23
 
24
24
  def_node_matcher :proc_node?, '(send (const {nil? cbase} :Proc) :new)'
25
+ def_node_matcher :symbol_proc_receiver?, '{(send ...) (super ...) zsuper}'
25
26
  def_node_matcher :symbol_proc?, <<~PATTERN
26
- ({block numblock}
27
- ${(send ...) (super ...) zsuper}
28
- ${(args (arg _)) %Integer}
29
- (send (lvar _var) $_))
27
+ {
28
+ (block $#symbol_proc_receiver? $(args (arg _var)) (send (lvar _var) $_))
29
+ (numblock $#symbol_proc_receiver? $1 (send (lvar :_1) $_))
30
+ }
30
31
  PATTERN
31
32
 
32
33
  def self.autocorrect_incompatible_with
@@ -33,7 +33,9 @@ module RuboCop
33
33
  end
34
34
 
35
35
  def add_parentheses(node, corrector)
36
- if node.arguments.empty?
36
+ if !node.respond_to?(:arguments)
37
+ corrector.wrap(node, '(', ')')
38
+ elsif node.arguments.empty?
37
39
  corrector.insert_after(node, '()')
38
40
  else
39
41
  corrector.replace(args_begin(node), '(')
@@ -66,7 +66,7 @@ module RuboCop
66
66
  add_configuration_options(opts)
67
67
  add_formatting_options(opts)
68
68
 
69
- option(opts, '-r', '--require FILE') { |f| require f }
69
+ option(opts, '-r', '--require FILE') { |f| require_feature(f) }
70
70
 
71
71
  add_severity_option(opts)
72
72
  add_flags_with_optional_args(opts)
@@ -92,14 +92,7 @@ module RuboCop
92
92
  raise OptionArgumentError, message
93
93
  end
94
94
 
95
- @options[:"#{option}"] =
96
- if list.empty?
97
- ['']
98
- else
99
- list.split(',').map do |c|
100
- Cop::Registry.qualified_cop_name(c, "--#{option} option")
101
- end
102
- end
95
+ @options[:"#{option}"] = list.empty? ? [''] : list.split(',')
103
96
  end
104
97
  end
105
98
 
@@ -239,6 +232,13 @@ module RuboCop
239
232
  long_opt[2..-1].sub('[no-]', '').sub(/ .*/, '')
240
233
  .tr('-', '_').gsub(/[\[\]]/, '').to_sym
241
234
  end
235
+
236
+ def require_feature(file)
237
+ # If any features were added on the CLI from `--require`,
238
+ # add them to the config.
239
+ ConfigLoader.add_loaded_features(file)
240
+ require file
241
+ end
242
242
  end
243
243
 
244
244
  # Validates option arguments and the options' compatibility with each other.
@@ -9,10 +9,6 @@ module CopHelper
9
9
  let(:ruby_version) { 2.4 }
10
10
  let(:rails_version) { false }
11
11
 
12
- def inspect_source_file(source)
13
- Tempfile.open('tmp') { |f| inspect_source(source, f) }
14
- end
15
-
16
12
  def inspect_source(source, file = nil)
17
13
  RuboCop::Formatter::DisabledConfigFormatter.config_to_allow_offenses = {}
18
14
  RuboCop::Formatter::DisabledConfigFormatter.detected_styles = {}
@@ -111,28 +111,9 @@ module RuboCop
111
111
  source
112
112
  end
113
113
 
114
- # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
115
114
  def expect_offense(source, file = nil, severity: nil, **replacements)
116
- source = format_offense(source, **replacements)
117
- RuboCop::Formatter::DisabledConfigFormatter
118
- .config_to_allow_offenses = {}
119
- RuboCop::Formatter::DisabledConfigFormatter.detected_styles = {}
120
- cop.instance_variable_get(:@options)[:auto_correct] = true
121
-
122
- expected_annotations = AnnotatedSource.parse(source)
123
-
124
- if expected_annotations.plain_source == source
125
- raise 'Use `expect_no_offenses` to assert that no offenses are found'
126
- end
127
-
128
- @processed_source = parse_source(expected_annotations.plain_source,
129
- file)
130
-
131
- unless @processed_source.valid_syntax?
132
- raise 'Error parsing example code: ' \
133
- "#{@processed_source.diagnostics.map(&:render).join("\n")}"
134
- end
135
-
115
+ expected_annotations = parse_annotations(source, **replacements)
116
+ @processed_source = parse_processed_source(expected_annotations.plain_source, file)
136
117
  @offenses = _investigate(cop, @processed_source)
137
118
  actual_annotations =
138
119
  expected_annotations.with_offense_annotations(@offenses)
@@ -143,7 +124,14 @@ module RuboCop
143
124
  @offenses
144
125
  end
145
126
 
146
- def expect_correction(correction, loop: true)
127
+ # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
128
+ def expect_correction(correction, loop: true, source: nil)
129
+ if source
130
+ expected_annotations = parse_annotations(source, raise_error: false)
131
+ @processed_source = parse_processed_source(expected_annotations.plain_source)
132
+ _investigate(cop, @processed_source)
133
+ end
134
+
147
135
  raise '`expect_correction` must follow `expect_offense`' unless @processed_source
148
136
 
149
137
  iteration = 0
@@ -192,6 +180,30 @@ module RuboCop
192
180
  expect(actual_annotations.to_s).to eq(source)
193
181
  end
194
182
 
183
+ def parse_annotations(source, raise_error: true, **replacements)
184
+ set_formatter_options
185
+
186
+ source = format_offense(source, **replacements)
187
+ annotations = AnnotatedSource.parse(source)
188
+ return annotations unless raise_error && annotations.plain_source == source
189
+
190
+ raise 'Use `expect_no_offenses` to assert that no offenses are found'
191
+ end
192
+
193
+ def parse_processed_source(source, file = nil)
194
+ processed_source = parse_source(source, file)
195
+ return processed_source if processed_source.valid_syntax?
196
+
197
+ raise 'Error parsing example code: ' \
198
+ "#{processed_source.diagnostics.map(&:render).join("\n")}"
199
+ end
200
+
201
+ def set_formatter_options
202
+ RuboCop::Formatter::DisabledConfigFormatter.config_to_allow_offenses = {}
203
+ RuboCop::Formatter::DisabledConfigFormatter.detected_styles = {}
204
+ cop.instance_variable_get(:@options)[:auto_correct] = true
205
+ end
206
+
195
207
  # Parsed representation of code annotated with the `^^^ Message` style
196
208
  class AnnotatedSource
197
209
  ANNOTATION_PATTERN = /\A\s*(\^+|\^{}) /.freeze