rubocop 1.8.1 → 1.9.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 (45) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  3. data/config/default.yml +34 -4
  4. data/lib/rubocop.rb +5 -0
  5. data/lib/rubocop/cli/command/auto_genenerate_config.rb +5 -4
  6. data/lib/rubocop/config.rb +2 -2
  7. data/lib/rubocop/config_loader.rb +7 -14
  8. data/lib/rubocop/config_store.rb +12 -1
  9. data/lib/rubocop/cop/base.rb +1 -1
  10. data/lib/rubocop/cop/generator.rb +1 -3
  11. data/lib/rubocop/cop/internal_affairs.rb +5 -1
  12. data/lib/rubocop/cop/internal_affairs/empty_line_between_expect_offense_and_correction.rb +68 -0
  13. data/lib/rubocop/cop/internal_affairs/example_description.rb +89 -0
  14. data/lib/rubocop/cop/internal_affairs/redundant_described_class_as_subject.rb +61 -0
  15. data/lib/rubocop/cop/internal_affairs/redundant_let_rubocop_config_new.rb +64 -0
  16. data/lib/rubocop/cop/layout/class_structure.rb +7 -2
  17. data/lib/rubocop/cop/lint/number_conversion.rb +41 -6
  18. data/lib/rubocop/cop/lint/numbered_parameter_assignment.rb +47 -0
  19. data/lib/rubocop/cop/lint/or_assignment_to_constant.rb +39 -0
  20. data/lib/rubocop/cop/lint/symbol_conversion.rb +102 -0
  21. data/lib/rubocop/cop/lint/triple_quotes.rb +71 -0
  22. data/lib/rubocop/cop/mixin/check_line_breakable.rb +5 -0
  23. data/lib/rubocop/cop/mixin/comments_help.rb +0 -1
  24. data/lib/rubocop/cop/naming/rescued_exceptions_variable_name.rb +38 -5
  25. data/lib/rubocop/cop/naming/variable_number.rb +1 -1
  26. data/lib/rubocop/cop/severity.rb +3 -3
  27. data/lib/rubocop/cop/style/ascii_comments.rb +1 -1
  28. data/lib/rubocop/cop/style/disable_cops_within_source_code_directive.rb +49 -9
  29. data/lib/rubocop/cop/style/eval_with_location.rb +63 -34
  30. data/lib/rubocop/cop/style/float_division.rb +3 -0
  31. data/lib/rubocop/cop/style/format_string_token.rb +18 -2
  32. data/lib/rubocop/cop/style/if_inside_else.rb +14 -7
  33. data/lib/rubocop/cop/style/if_with_boolean_literal_branches.rb +96 -0
  34. data/lib/rubocop/cop/style/nil_comparison.rb +1 -0
  35. data/lib/rubocop/cop/style/non_nil_check.rb +23 -13
  36. data/lib/rubocop/cop/style/single_line_methods.rb +1 -1
  37. data/lib/rubocop/cop/style/sole_nested_conditional.rb +26 -2
  38. data/lib/rubocop/cop/style/ternary_parentheses.rb +1 -1
  39. data/lib/rubocop/formatter/git_hub_actions_formatter.rb +1 -0
  40. data/lib/rubocop/magic_comment.rb +30 -1
  41. data/lib/rubocop/options.rb +1 -1
  42. data/lib/rubocop/rspec/expect_offense.rb +5 -2
  43. data/lib/rubocop/runner.rb +1 -0
  44. data/lib/rubocop/version.rb +2 -2
  45. metadata +12 -3
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module InternalAffairs
6
+ # This cop checks for redundant `subject(:cop) { described_class.new }`.
7
+ #
8
+ # @example
9
+ # # bad
10
+ # RSpec.describe RuboCop::Cop::Department::Foo do
11
+ # subject(:cop) { described_class.new(config) }
12
+ # end
13
+ #
14
+ # # good
15
+ # RSpec.describe RuboCop::Cop::Department::Foo, :config do
16
+ # end
17
+ #
18
+ class RedundantDescribedClassAsSubject < Base
19
+ include RangeHelp
20
+ extend AutoCorrector
21
+
22
+ MSG = 'Remove the redundant `subject`%<additional_message>s.'
23
+
24
+ def_node_matcher :described_class_subject?, <<~PATTERN
25
+ (block
26
+ (send nil? :subject
27
+ (sym :cop))
28
+ (args)
29
+ (send
30
+ (send nil? :described_class) :new
31
+ $...))
32
+ PATTERN
33
+
34
+ def on_block(node)
35
+ return unless (described_class_arguments = described_class_subject?(node))
36
+ return if described_class_arguments.count >= 2
37
+
38
+ describe = find_describe_method_node(node)
39
+
40
+ unless (exist_config = describe.last_argument.source == ':config')
41
+ additional_message = ' and specify `:config` in `describe`'
42
+ end
43
+
44
+ message = format(MSG, additional_message: additional_message)
45
+
46
+ add_offense(node, message: message) do |corrector|
47
+ corrector.remove(range_by_whole_lines(node.source_range, include_final_newline: true))
48
+
49
+ corrector.insert_after(describe.last_argument, ', :config') unless exist_config
50
+ end
51
+ end
52
+
53
+ private
54
+
55
+ def find_describe_method_node(block_node)
56
+ block_node.ancestors.find { |node| node.block_type? && node.method?(:describe) }.send_node
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module InternalAffairs
6
+ # This cop checks that `let` is `RuboCop::Config.new` with no arguments.
7
+ #
8
+ # @example
9
+ # # bad
10
+ # RSpec.describe RuboCop::Cop::Department::Foo, :config do
11
+ # let(:config) { RuboCop::Config.new }
12
+ # end
13
+ #
14
+ # # good
15
+ # RSpec.describe RuboCop::Cop::Department::Foo, :config do
16
+ # end
17
+ #
18
+ # RSpec.describe RuboCop::Cop::Department::Foo, :config do
19
+ # let(:config) { RuboCop::Config.new(argument) }
20
+ # end
21
+ #
22
+ class RedundantLetRuboCopConfigNew < Base
23
+ include RangeHelp
24
+ extend AutoCorrector
25
+
26
+ MSG = 'Remove `let` that is `RuboCop::Config.new` with no arguments%<additional_message>s.'
27
+
28
+ def_node_matcher :let_rubocop_config_new?, <<~PATTERN
29
+ (block
30
+ (send nil? :let
31
+ (sym :config))
32
+ (args)
33
+ (send
34
+ (const
35
+ (const nil? :RuboCop) :Config) :new))
36
+ PATTERN
37
+
38
+ def on_block(node)
39
+ return unless let_rubocop_config_new?(node)
40
+
41
+ describe = find_describe_method_node(node)
42
+
43
+ unless (exist_config = describe.last_argument.source == ':config')
44
+ additional_message = ' and specify `:config` in `describe`'
45
+ end
46
+
47
+ message = format(MSG, additional_message: additional_message)
48
+
49
+ add_offense(node, message: message) do |corrector|
50
+ corrector.remove(range_by_whole_lines(node.source_range, include_final_newline: true))
51
+
52
+ corrector.insert_after(describe.last_argument, ', :config') unless exist_config
53
+ end
54
+ end
55
+
56
+ private
57
+
58
+ def find_describe_method_node(block_node)
59
+ block_node.ancestors.find { |node| node.block_type? && node.method?(:describe) }.send_node
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
@@ -213,7 +213,12 @@ module RuboCop
213
213
  name = node.method_name.to_s
214
214
  category, = categories.find { |_, names| names.include?(name) }
215
215
  key = category || name
216
- visibility_key = "#{node_visibility(node)}_#{key}"
216
+ visibility_key =
217
+ if node.def_modifier?
218
+ "#{name}_methods"
219
+ else
220
+ "#{node_visibility(node)}_#{key}"
221
+ end
217
222
  expected_order.include?(visibility_key) ? visibility_key : key
218
223
  end
219
224
 
@@ -264,7 +269,7 @@ module RuboCop
264
269
 
265
270
  def source_range_with_comment(node)
266
271
  begin_pos, end_pos =
267
- if node.def_type?
272
+ if node.def_type? && !node.method?(:initialize) || node.send_type? && node.def_modifier?
268
273
  start_node = find_visibility_start(node) || node
269
274
  end_node = find_visibility_end(node) || node
270
275
  [begin_pos_with_comment(start_node),
@@ -25,12 +25,18 @@ module RuboCop
25
25
  # '10'.to_i
26
26
  # '10.2'.to_f
27
27
  # '10'.to_c
28
+ # ['1', '2', '3'].map(&:to_i)
29
+ # foo.try(:to_f)
30
+ # bar.send(:to_c)
28
31
  #
29
32
  # # good
30
33
  #
31
34
  # Integer('10', 10)
32
35
  # Float('10.2')
33
36
  # Complex('10')
37
+ # ['1', '2', '3'].map { |i| Integer(i, 10) }
38
+ # foo.try { |i| Float(i) }
39
+ # bar.send { |i| Complex(i) }
34
40
  #
35
41
  # @example IgnoredMethods: [minutes]
36
42
  #
@@ -52,22 +58,33 @@ module RuboCop
52
58
  }.freeze
53
59
  MSG = 'Replace unsafe number conversion with number '\
54
60
  'class parsing, instead of using '\
55
- '%<number_object>s.%<to_method>s, use stricter '\
61
+ '%<current>s, use stricter '\
56
62
  '%<corrected_method>s.'
57
- RESTRICT_ON_SEND = CONVERSION_METHOD_CLASS_MAPPING.keys.freeze
63
+ METHODS = CONVERSION_METHOD_CLASS_MAPPING.keys.map(&:inspect).join(' ')
58
64
 
59
65
  def_node_matcher :to_method, <<~PATTERN
60
- (send $_ ${:to_i :to_f :to_c})
66
+ (send $_ ${#{METHODS}})
67
+ PATTERN
68
+
69
+ def_node_matcher :to_method_symbol, <<~PATTERN
70
+ {(send _ $_ ${(sym ${#{METHODS}})} ...)
71
+ (send _ $_ ${(block_pass (sym ${#{METHODS}}))} ...)}
61
72
  PATTERN
62
73
 
63
74
  def on_send(node)
75
+ handle_conversion_method(node)
76
+ handle_as_symbol(node)
77
+ end
78
+
79
+ private
80
+
81
+ def handle_conversion_method(node)
64
82
  to_method(node) do |receiver, to_method|
65
83
  next if receiver.nil? || ignore_receiver?(receiver)
66
84
 
67
85
  message = format(
68
86
  MSG,
69
- number_object: receiver.source,
70
- to_method: to_method,
87
+ current: "#{receiver.source}.#{to_method}",
71
88
  corrected_method: correct_method(node, receiver)
72
89
  )
73
90
  add_offense(node, message: message) do |corrector|
@@ -76,13 +93,31 @@ module RuboCop
76
93
  end
77
94
  end
78
95
 
79
- private
96
+ def handle_as_symbol(node)
97
+ to_method_symbol(node) do |receiver, sym_node, to_method|
98
+ next if receiver.nil?
99
+
100
+ message = format(
101
+ MSG,
102
+ current: sym_node.source,
103
+ corrected_method: correct_sym_method(to_method)
104
+ )
105
+ add_offense(node, message: message) do |corrector|
106
+ corrector.replace(sym_node, correct_sym_method(to_method))
107
+ end
108
+ end
109
+ end
80
110
 
81
111
  def correct_method(node, receiver)
82
112
  format(CONVERSION_METHOD_CLASS_MAPPING[node.method_name],
83
113
  number_object: receiver.source)
84
114
  end
85
115
 
116
+ def correct_sym_method(to_method)
117
+ body = format(CONVERSION_METHOD_CLASS_MAPPING[to_method], number_object: 'i')
118
+ "{ |i| #{body} }"
119
+ end
120
+
86
121
  def ignore_receiver?(receiver)
87
122
  if receiver.send_type? && ignored_method?(receiver.method_name)
88
123
  true
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Lint
6
+ # This cop checks for uses of numbered parameter assignment.
7
+ # It emulates the following warning in Ruby 2.7:
8
+ #
9
+ # % ruby -ve '_1 = :value'
10
+ # ruby 2.7.2p137 (2020-10-01 revision 5445e04352) [x86_64-darwin19]
11
+ # -e:1: warning: `_1' is reserved for numbered parameter; consider another name
12
+ #
13
+ # Assiging to numbered parameter (from `_1` to `_9`) cause an error in Ruby 3.0.
14
+ #
15
+ # % ruby -ve '_1 = :value'
16
+ # ruby 3.0.0p0 (2020-12-25 revision 95aff21468) [x86_64-darwin19]
17
+ # -e:1: _1 is reserved for numbered parameter
18
+ #
19
+ # NOTE: The parametered parameters are from `_1` to `_9`. This cop checks `_0`, and over `_10`
20
+ # as well to prevent confusion.
21
+ #
22
+ # @example
23
+ #
24
+ # # bad
25
+ # _1 = :value
26
+ #
27
+ # # good
28
+ # non_numbered_parameter_name = :value
29
+ #
30
+ class NumberedParameterAssignment < Base
31
+ NUM_PARAM_MSG = '`_%<number>s` is reserved for numbered parameter; consider another name.'
32
+ LVAR_MSG = '`_%<number>s` is similar to numbered parameter; consider another name.'
33
+ NUMBERED_PARAMETER_RANGE = (1..9).freeze
34
+
35
+ def on_lvasgn(node)
36
+ lhs, _rhs = *node
37
+ return unless /\A_(\d+)\z/ =~ lhs
38
+
39
+ number = Regexp.last_match(1).to_i
40
+ template = NUMBERED_PARAMETER_RANGE.include?(number) ? NUM_PARAM_MSG : LVAR_MSG
41
+
42
+ add_offense(node, message: format(template, number: number))
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Lint
6
+ # This cop checks for unintended or-assignment to a constant.
7
+ #
8
+ # Constants should always be assigned in the same location. And its value
9
+ # should always be the same. If constants are assigned in multiple
10
+ # locations, the result may vary depending on the order of `require`.
11
+ #
12
+ # Also, if you already have such an implementation, auto-correction may
13
+ # change the result.
14
+ #
15
+ # @example
16
+ #
17
+ # # bad
18
+ # CONST ||= 1
19
+ #
20
+ # # good
21
+ # CONST = 1
22
+ #
23
+ class OrAssignmentToConstant < Base
24
+ extend AutoCorrector
25
+
26
+ MSG = 'Avoid using or-assignment with constants.'
27
+
28
+ def on_or_asgn(node)
29
+ lhs, _rhs = *node
30
+ return unless lhs&.casgn_type?
31
+
32
+ add_offense(node.loc.operator) do |corrector|
33
+ corrector.replace(node.loc.operator, '=')
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,102 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Lint
6
+ # This cop checks for uses of literal strings converted to
7
+ # a symbol where a literal symbol could be used instead.
8
+ #
9
+ # @example
10
+ # # bad
11
+ # 'string'.to_sym
12
+ # :symbol.to_sym
13
+ # 'underscored_string'.to_sym
14
+ # :'underscored_symbol'
15
+ # 'hyphenated-string'.to_sym
16
+ #
17
+ # # good
18
+ # :string
19
+ # :symbol
20
+ # :underscored_string
21
+ # :underscored_symbol
22
+ # :'hyphenated-string'
23
+ #
24
+ class SymbolConversion < Base
25
+ extend AutoCorrector
26
+
27
+ MSG = 'Unnecessary symbol conversion; use `%<correction>s` instead.'
28
+ RESTRICT_ON_SEND = %i[to_sym intern].freeze
29
+
30
+ def on_send(node)
31
+ return unless node.receiver.str_type? || node.receiver.sym_type?
32
+
33
+ register_offense(node, correction: node.receiver.value.to_sym.inspect)
34
+ end
35
+
36
+ def on_sym(node)
37
+ return if properly_quoted?(node.source, node.value.inspect)
38
+
39
+ # `alias` arguments are symbols but since a symbol that requires
40
+ # being quoted is not a valid method identifier, it can be ignored
41
+ return if in_alias?(node)
42
+
43
+ # The `%I[]` and `%i[]` macros are parsed as normal arrays of symbols
44
+ # so they need to be ignored.
45
+ return if in_percent_literal_array?(node)
46
+
47
+ # Symbol hash keys have a different format and need to be handled separately
48
+ return correct_hash_key(node) if hash_key?(node)
49
+
50
+ register_offense(node, correction: node.value.inspect)
51
+ end
52
+
53
+ private
54
+
55
+ def register_offense(node, correction:, message: format(MSG, correction: correction))
56
+ add_offense(node, message: message) do |corrector|
57
+ corrector.replace(node, correction)
58
+ end
59
+ end
60
+
61
+ def properly_quoted?(source, value)
62
+ return true unless source.match?(/['"]/)
63
+
64
+ source == value ||
65
+ # `Symbol#inspect` uses double quotes, but allow single-quoted
66
+ # symbols to work as well.
67
+ source.tr("'", '"') == value
68
+ end
69
+
70
+ def in_alias?(node)
71
+ node.parent&.alias_type?
72
+ end
73
+
74
+ def in_percent_literal_array?(node)
75
+ node.parent&.array_type? && node.parent&.percent_literal?
76
+ end
77
+
78
+ def hash_key?(node)
79
+ node.parent&.pair_type? && node == node.parent.child_nodes.first
80
+ end
81
+
82
+ def correct_hash_key(node)
83
+ # Although some operators can be converted to symbols normally
84
+ # (ie. `:==`), these are not accepted as hash keys and will
85
+ # raise a syntax error (eg. `{ ==: ... }`). Therefore, if the
86
+ # symbol does not start with an alpha-numeric or underscore, it
87
+ # will be ignored.
88
+ return unless node.value.to_s.match?(/\A[a-z0-9_]/i)
89
+
90
+ correction = node.value.inspect.delete(':')
91
+ return if properly_quoted?(node.source, correction)
92
+
93
+ register_offense(
94
+ node,
95
+ correction: correction,
96
+ message: format(MSG, correction: "#{correction}:")
97
+ )
98
+ end
99
+ end
100
+ end
101
+ end
102
+ end
@@ -0,0 +1,71 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Lint
6
+ # This cop checks for "triple quotes" (strings delimted by any odd number
7
+ # of quotes greater than 1).
8
+ #
9
+ # Ruby allows multiple strings to be implicitly concatenated by just
10
+ # being adjacent in a statement (ie. `"foo""bar" == "foobar"`). This sometimes
11
+ # gives the impression that there is something special about triple quotes, but
12
+ # in fact it is just extra unnecessary quotes and produces the same string. Each
13
+ # pair of quotes produces an additional concatenated empty string, so the result
14
+ # is still only the "actual" string within the delimiters.
15
+ #
16
+ # NOTE: Although this cop is called triple quotes, the same behavior is present
17
+ # for strings delimited by 5, 7, etc. quotation marks.
18
+ #
19
+ # @example
20
+ # # bad
21
+ # """
22
+ # A string
23
+ # """
24
+ #
25
+ # # bad
26
+ # '''
27
+ # A string
28
+ # '''
29
+ #
30
+ # # good
31
+ # "
32
+ # A string
33
+ # "
34
+ #
35
+ # # good
36
+ # <<STRING
37
+ # A string
38
+ # STRING
39
+ #
40
+ # # good (but not the same spacing as the bad case)
41
+ # 'A string'
42
+ class TripleQuotes < Base
43
+ extend AutoCorrector
44
+
45
+ MSG = 'Delimiting a string with multiple quotes has no effect, use a single quote instead.'
46
+
47
+ def on_dstr(node)
48
+ return if (empty_str_nodes = empty_str_nodes(node)).none?
49
+
50
+ opening_quotes = node.source.scan(/(?<=\A)['"]*/)[0]
51
+ return if opening_quotes.size < 3
52
+
53
+ # If the node is composed of only empty `str` nodes, keep one
54
+ empty_str_nodes.shift if empty_str_nodes.size == node.child_nodes.size
55
+
56
+ add_offense(node) do |corrector|
57
+ empty_str_nodes.each do |str|
58
+ corrector.remove(str)
59
+ end
60
+ end
61
+ end
62
+
63
+ private
64
+
65
+ def empty_str_nodes(node)
66
+ node.each_child_node(:str).select { |str| str.value == '' }
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end