rubocop 1.23.0 → 1.24.1

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 (70) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -2
  3. data/config/default.yml +46 -1
  4. data/lib/rubocop/cli/command/auto_genenerate_config.rb +1 -1
  5. data/lib/rubocop/cli/command/init_dotfile.rb +1 -1
  6. data/lib/rubocop/cli/command/show_docs_url.rb +48 -0
  7. data/lib/rubocop/cli/command/suggest_extensions.rb +1 -1
  8. data/lib/rubocop/cli.rb +1 -0
  9. data/lib/rubocop/config_loader_resolver.rb +1 -1
  10. data/lib/rubocop/cop/bundler/duplicated_gem.rb +1 -1
  11. data/lib/rubocop/cop/correctors/each_to_for_corrector.rb +1 -1
  12. data/lib/rubocop/cop/correctors/if_then_corrector.rb +55 -0
  13. data/lib/rubocop/cop/documentation.rb +19 -2
  14. data/lib/rubocop/cop/gemspec/require_mfa.rb +8 -10
  15. data/lib/rubocop/cop/internal_affairs/redundant_method_dispatch_node.rb +47 -0
  16. data/lib/rubocop/cop/internal_affairs/undefined_config.rb +3 -1
  17. data/lib/rubocop/cop/internal_affairs.rb +1 -0
  18. data/lib/rubocop/cop/layout/comment_indentation.rb +31 -2
  19. data/lib/rubocop/cop/layout/dot_position.rb +4 -0
  20. data/lib/rubocop/cop/layout/hash_alignment.rb +1 -1
  21. data/lib/rubocop/cop/layout/space_after_colon.rb +1 -1
  22. data/lib/rubocop/cop/layout/space_before_first_arg.rb +4 -0
  23. data/lib/rubocop/cop/lint/constant_definition_in_block.rb +1 -1
  24. data/lib/rubocop/cop/lint/deprecated_class_methods.rb +16 -4
  25. data/lib/rubocop/cop/lint/deprecated_open_ssl_constant.rb +6 -0
  26. data/lib/rubocop/cop/lint/each_with_object_argument.rb +1 -1
  27. data/lib/rubocop/cop/lint/incompatible_io_select_with_fiber_scheduler.rb +4 -0
  28. data/lib/rubocop/cop/lint/parentheses_as_grouped_expression.rb +7 -4
  29. data/lib/rubocop/cop/metrics/block_length.rb +1 -0
  30. data/lib/rubocop/cop/metrics/cyclomatic_complexity.rb +0 -9
  31. data/lib/rubocop/cop/metrics/method_length.rb +1 -0
  32. data/lib/rubocop/cop/metrics/module_length.rb +1 -1
  33. data/lib/rubocop/cop/metrics/utils/code_length_calculator.rb +1 -1
  34. data/lib/rubocop/cop/metrics/utils/repeated_attribute_discount.rb +1 -1
  35. data/lib/rubocop/cop/mixin/enforce_superclass.rb +5 -0
  36. data/lib/rubocop/cop/mixin/hash_alignment_styles.rb +4 -3
  37. data/lib/rubocop/cop/mixin/hash_shorthand_syntax.rb +56 -0
  38. data/lib/rubocop/cop/naming/block_forwarding.rb +107 -0
  39. data/lib/rubocop/cop/security/open.rb +11 -1
  40. data/lib/rubocop/cop/style/character_literal.rb +8 -1
  41. data/lib/rubocop/cop/style/collection_compact.rb +31 -13
  42. data/lib/rubocop/cop/style/combinable_loops.rb +2 -2
  43. data/lib/rubocop/cop/style/empty_case_condition.rb +10 -0
  44. data/lib/rubocop/cop/style/file_read.rb +112 -0
  45. data/lib/rubocop/cop/style/file_write.rb +124 -0
  46. data/lib/rubocop/cop/style/hash_conversion.rb +2 -1
  47. data/lib/rubocop/cop/style/hash_syntax.rb +22 -0
  48. data/lib/rubocop/cop/style/hash_transform_keys.rb +6 -6
  49. data/lib/rubocop/cop/style/hash_transform_values.rb +6 -6
  50. data/lib/rubocop/cop/style/if_inside_else.rb +15 -0
  51. data/lib/rubocop/cop/style/map_to_hash.rb +68 -0
  52. data/lib/rubocop/cop/style/method_call_with_args_parentheses/omit_parentheses.rb +13 -0
  53. data/lib/rubocop/cop/style/method_call_with_args_parentheses.rb +3 -1
  54. data/lib/rubocop/cop/style/method_def_parentheses.rb +17 -13
  55. data/lib/rubocop/cop/style/numeric_literals.rb +10 -1
  56. data/lib/rubocop/cop/style/one_line_conditional.rb +18 -39
  57. data/lib/rubocop/cop/style/redundant_interpolation.rb +17 -3
  58. data/lib/rubocop/cop/style/redundant_regexp_character_class.rb +5 -1
  59. data/lib/rubocop/cop/style/redundant_self.rb +1 -1
  60. data/lib/rubocop/cop/style/safe_navigation.rb +1 -5
  61. data/lib/rubocop/cop/style/single_line_block_params.rb +2 -2
  62. data/lib/rubocop/cop/style/sole_nested_conditional.rb +3 -1
  63. data/lib/rubocop/cop/team.rb +1 -1
  64. data/lib/rubocop/cop/util.rb +9 -1
  65. data/lib/rubocop/options.rb +6 -1
  66. data/lib/rubocop/remote_config.rb +1 -3
  67. data/lib/rubocop/result_cache.rb +1 -1
  68. data/lib/rubocop/version.rb +1 -1
  69. data/lib/rubocop.rb +7 -0
  70. metadata +13 -5
@@ -0,0 +1,124 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Style
6
+ # Favor `File.(bin)write` convenience methods.
7
+ #
8
+ # @example
9
+ # ## text mode
10
+ # # bad
11
+ # File.open(filename, 'w').write(content)
12
+ # File.open(filename, 'w') do |f|
13
+ # f.write(content)
14
+ # end
15
+ #
16
+ # # good
17
+ # File.write(filename, content)
18
+ #
19
+ # @example
20
+ # ## binary mode
21
+ # # bad
22
+ # File.open(filename, 'wb').write(content)
23
+ # File.open(filename, 'wb') do |f|
24
+ # f.write(content)
25
+ # end
26
+ #
27
+ # # good
28
+ # File.binwrite(filename, content)
29
+ #
30
+ class FileWrite < Base
31
+ extend AutoCorrector
32
+ include RangeHelp
33
+
34
+ MSG = 'Use `File.%<write_method>s`.'
35
+
36
+ RESTRICT_ON_SEND = %i[open].to_set.freeze
37
+
38
+ TRUNCATING_WRITE_MODES = %w[w wt wb w+ w+t w+b].to_set.freeze
39
+
40
+ # @!method file_open?(node)
41
+ def_node_matcher :file_open?, <<~PATTERN
42
+ (send
43
+ (const {nil? cbase} :File)
44
+ :open
45
+ $_
46
+ (str $%TRUNCATING_WRITE_MODES)
47
+ (block-pass (sym :write))?
48
+ )
49
+ PATTERN
50
+
51
+ # @!method send_write?(node)
52
+ def_node_matcher :send_write?, <<~PATTERN
53
+ (send _ :write $_)
54
+ PATTERN
55
+
56
+ # @!method block_write?(node)
57
+ def_node_matcher :block_write?, <<~PATTERN
58
+ (block _ (args (arg $_)) (send (lvar $_) :write $_))
59
+ PATTERN
60
+
61
+ def on_send(node)
62
+ evidence(node) do |filename, mode, content, write_node|
63
+ message = format(MSG, write_method: write_method(mode))
64
+
65
+ add_offense(write_node, message: message) do |corrector|
66
+ range = range_between(node.loc.selector.begin_pos, write_node.loc.expression.end_pos)
67
+ replacement = replacement(mode, filename, content, write_node)
68
+
69
+ corrector.replace(range, replacement)
70
+ end
71
+ end
72
+ end
73
+
74
+ def evidence(node)
75
+ file_open?(node) do |filename, mode|
76
+ file_open_write?(node.parent) do |content|
77
+ yield(filename, mode, content, node.parent)
78
+ end
79
+ end
80
+ end
81
+
82
+ private
83
+
84
+ def file_open_write?(node)
85
+ content = send_write?(node) || block_write?(node) do |block_arg, lvar, write_arg|
86
+ write_arg if block_arg == lvar
87
+ end
88
+
89
+ yield(content) if content
90
+ end
91
+
92
+ def write_method(mode)
93
+ mode.end_with?('b') ? :binwrite : :write
94
+ end
95
+
96
+ def replacement(mode, filename, content, write_node)
97
+ replacement = "#{write_method(mode)}(#{filename.source}, #{content.source})"
98
+
99
+ if heredoc?(write_node)
100
+ first_argument = write_node.body.first_argument
101
+
102
+ <<~REPLACEMENT.chomp
103
+ #{replacement}
104
+ #{heredoc_range(first_argument).source}
105
+ REPLACEMENT
106
+ else
107
+ replacement
108
+ end
109
+ end
110
+
111
+ def heredoc?(write_node)
112
+ write_node.block_type? && (first_argument = write_node.body.first_argument) &&
113
+ first_argument.respond_to?(:heredoc?) && first_argument.heredoc?
114
+ end
115
+
116
+ def heredoc_range(first_argument)
117
+ range_between(
118
+ first_argument.loc.heredoc_body.begin_pos, first_argument.loc.heredoc_end.end_pos
119
+ )
120
+ end
121
+ end
122
+ end
123
+ end
124
+ end
@@ -101,7 +101,8 @@ module RuboCop
101
101
  end
102
102
 
103
103
  def requires_parens?(node)
104
- node.call_type? && node.arguments.any? && !node.parenthesized?
104
+ (node.call_type? && node.arguments.any? && !node.parenthesized?) ||
105
+ node.or_type? || node.and_type?
105
106
  end
106
107
 
107
108
  def multi_argument(node)
@@ -19,6 +19,10 @@ module RuboCop
19
19
  # * ruby19_no_mixed_keys - forces use of ruby 1.9 syntax and forbids mixed
20
20
  # syntax hashes
21
21
  #
22
+ # This cop has `EnforcedShorthandSyntax` option.
23
+ # It can enforce either the use of the explicit hash value syntax or
24
+ # the use of Ruby 3.1's hash value shorthand syntax.
25
+ #
22
26
  # @example EnforcedStyle: ruby19 (default)
23
27
  # # bad
24
28
  # {:a => 2}
@@ -54,8 +58,26 @@ module RuboCop
54
58
  # # good
55
59
  # {a: 1, b: 2}
56
60
  # {:c => 3, 'd' => 4}
61
+ #
62
+ # @example EnforcedShorthandSyntax: always (default)
63
+ #
64
+ # # bad
65
+ # {foo: foo, bar: bar}
66
+ #
67
+ # # good
68
+ # {foo:, bar:}
69
+ #
70
+ # @example EnforcedShorthandSyntax: never
71
+ #
72
+ # # bad
73
+ # {foo:, bar:}
74
+ #
75
+ # # good
76
+ # {foo: foo, bar: bar}
77
+ #
57
78
  class HashSyntax < Base
58
79
  include ConfigurableEnforcedStyle
80
+ include HashShorthandSyntax
59
81
  include RangeHelp
60
82
  extend AutoCorrector
61
83
 
@@ -30,13 +30,13 @@ module RuboCop
30
30
  # @!method on_bad_each_with_object(node)
31
31
  def_node_matcher :on_bad_each_with_object, <<~PATTERN
32
32
  (block
33
- ({send csend} !#array_receiver? :each_with_object (hash))
33
+ (call !#array_receiver? :each_with_object (hash))
34
34
  (args
35
35
  (mlhs
36
36
  (arg $_)
37
37
  (arg _val))
38
38
  (arg _memo))
39
- ({send csend} (lvar _memo) :[]= $!`_memo $(lvar _val)))
39
+ (call (lvar _memo) :[]= $!`_memo $(lvar _val)))
40
40
  PATTERN
41
41
 
42
42
  # @!method on_bad_hash_brackets_map(node)
@@ -45,7 +45,7 @@ module RuboCop
45
45
  (const _ :Hash)
46
46
  :[]
47
47
  (block
48
- ({send csend} !#array_receiver? {:map :collect})
48
+ (call !#array_receiver? {:map :collect})
49
49
  (args
50
50
  (arg $_)
51
51
  (arg _val))
@@ -54,9 +54,9 @@ module RuboCop
54
54
 
55
55
  # @!method on_bad_map_to_h(node)
56
56
  def_node_matcher :on_bad_map_to_h, <<~PATTERN
57
- ({send csend}
57
+ (call
58
58
  (block
59
- ({send csend} !#array_receiver? {:map :collect})
59
+ (call !#array_receiver? {:map :collect})
60
60
  (args
61
61
  (arg $_)
62
62
  (arg _val))
@@ -67,7 +67,7 @@ module RuboCop
67
67
  # @!method on_bad_to_h(node)
68
68
  def_node_matcher :on_bad_to_h, <<~PATTERN
69
69
  (block
70
- ({send csend} !#array_receiver? :to_h)
70
+ (call !#array_receiver? :to_h)
71
71
  (args
72
72
  (arg $_)
73
73
  (arg _val))
@@ -30,13 +30,13 @@ module RuboCop
30
30
  # @!method on_bad_each_with_object(node)
31
31
  def_node_matcher :on_bad_each_with_object, <<~PATTERN
32
32
  (block
33
- ({send csend} !#array_receiver? :each_with_object (hash))
33
+ (call !#array_receiver? :each_with_object (hash))
34
34
  (args
35
35
  (mlhs
36
36
  (arg _key)
37
37
  (arg $_))
38
38
  (arg _memo))
39
- ({send csend} (lvar _memo) :[]= $(lvar _key) $!`_memo))
39
+ (call (lvar _memo) :[]= $(lvar _key) $!`_memo))
40
40
  PATTERN
41
41
 
42
42
  # @!method on_bad_hash_brackets_map(node)
@@ -45,7 +45,7 @@ module RuboCop
45
45
  (const _ :Hash)
46
46
  :[]
47
47
  (block
48
- ({send csend} !#array_receiver? {:map :collect})
48
+ (call !#array_receiver? {:map :collect})
49
49
  (args
50
50
  (arg _key)
51
51
  (arg $_))
@@ -54,9 +54,9 @@ module RuboCop
54
54
 
55
55
  # @!method on_bad_map_to_h(node)
56
56
  def_node_matcher :on_bad_map_to_h, <<~PATTERN
57
- ({send csend}
57
+ (call
58
58
  (block
59
- ({send csend} !#array_receiver? {:map :collect})
59
+ (call !#array_receiver? {:map :collect})
60
60
  (args
61
61
  (arg _key)
62
62
  (arg $_))
@@ -67,7 +67,7 @@ module RuboCop
67
67
  # @!method on_bad_to_h(node)
68
68
  def_node_matcher :on_bad_to_h, <<~PATTERN
69
69
  (block
70
- ({send csend} !#array_receiver? :to_h)
70
+ (call !#array_receiver? :to_h)
71
71
  (args
72
72
  (arg _key)
73
73
  (arg $_))
@@ -80,11 +80,19 @@ module RuboCop
80
80
  private
81
81
 
82
82
  def autocorrect(corrector, node)
83
+ if then?(node)
84
+ # If the nested `if` is a then node, correct it first,
85
+ # then the next pass will use `correct_to_elsif_from_if_inside_else_form`
86
+ IfThenCorrector.new(node, indentation: 0).call(corrector)
87
+ return
88
+ end
89
+
83
90
  if node.modifier_form?
84
91
  correct_to_elsif_from_modifier_form(corrector, node)
85
92
  else
86
93
  correct_to_elsif_from_if_inside_else_form(corrector, node, node.condition)
87
94
  end
95
+
88
96
  corrector.remove(range_by_whole_lines(find_end_range(node), include_final_newline: true))
89
97
  return unless (if_branch = node.if_branch)
90
98
 
@@ -102,15 +110,22 @@ module RuboCop
102
110
 
103
111
  def correct_to_elsif_from_if_inside_else_form(corrector, node, condition)
104
112
  corrector.replace(node.parent.loc.else, "elsif #{condition.source}")
113
+
105
114
  if_condition_range = if_condition_range(node, condition)
115
+
106
116
  if (if_branch = node.if_branch)
107
117
  corrector.replace(if_condition_range, if_branch.source)
108
118
  else
109
119
  corrector.remove(range_by_whole_lines(if_condition_range, include_final_newline: true))
110
120
  end
121
+
111
122
  corrector.remove(condition)
112
123
  end
113
124
 
125
+ def then?(node)
126
+ node.loc.begin&.source == 'then'
127
+ end
128
+
114
129
  def find_end_range(node)
115
130
  end_range = node.loc.end
116
131
  return end_range if end_range
@@ -0,0 +1,68 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Style
6
+ # This cop looks for uses of `map.to_h` or `collect.to_h` that could be
7
+ # written with just `to_h` in Ruby >= 2.6.
8
+ #
9
+ # NOTE: `Style/HashTransformKeys` and `Style/HashTransformValues` will
10
+ # also change this pattern if only hash keys or hash values are being
11
+ # transformed.
12
+ #
13
+ # @safety
14
+ # This cop is unsafe, as it can produce false positives if the receiver
15
+ # is not an `Enumerable`.
16
+ #
17
+ # @example
18
+ # # bad
19
+ # something.map { |v| [v, v * 2] }.to_h
20
+ #
21
+ # # good
22
+ # something.to_h { |v| [v, v * 2] }
23
+ #
24
+ # # bad
25
+ # {foo: bar}.collect { |k, v| [k.to_s, v.do_something] }.to_h
26
+ #
27
+ # # good
28
+ # {foo: bar}.to_h { |k, v| [k.to_s, v.do_something] }
29
+ #
30
+ class MapToHash < Base
31
+ extend AutoCorrector
32
+ extend TargetRubyVersion
33
+ include RangeHelp
34
+
35
+ minimum_target_ruby_version 2.6
36
+
37
+ MSG = 'Pass a block to `to_h` instead of calling `%<method>s.to_h`.'
38
+ RESTRICT_ON_SEND = %i[to_h].freeze
39
+
40
+ # @!method map_to_h?(node)
41
+ def_node_matcher :map_to_h?, <<~PATTERN
42
+ $(send (block $(send _ {:map :collect}) ...) :to_h)
43
+ PATTERN
44
+
45
+ def on_send(node)
46
+ return unless (to_h_node, map_node = map_to_h?(node))
47
+
48
+ message = format(MSG, method: map_node.loc.selector.source)
49
+ add_offense(map_node.loc.selector, message: message) do |corrector|
50
+ # If the `to_h` call already has a block, do not auto-correct.
51
+ next if to_h_node.block_node
52
+
53
+ autocorrect(corrector, to_h_node, map_node)
54
+ end
55
+ end
56
+
57
+ private
58
+
59
+ def autocorrect(corrector, to_h, map)
60
+ removal_range = range_between(to_h.loc.dot.begin_pos, to_h.loc.selector.end_pos)
61
+
62
+ corrector.remove(range_with_surrounding_space(range: removal_range, side: :left))
63
+ corrector.replace(map.loc.selector, 'to_h')
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end
@@ -13,9 +13,11 @@ module RuboCop
13
13
 
14
14
  private
15
15
 
16
+ # rubocop:disable Metrics/PerceivedComplexity
16
17
  def omit_parentheses(node)
17
18
  return unless node.parenthesized?
18
19
  return if inside_endless_method_def?(node)
20
+ return if require_parentheses_for_hash_value_omission?(node)
19
21
  return if syntax_like_method_call?(node)
20
22
  return if super_call_without_arguments?(node)
21
23
  return if allowed_camel_case_method_call?(node)
@@ -26,6 +28,7 @@ module RuboCop
26
28
  auto_correct(corrector, node)
27
29
  end
28
30
  end
31
+ # rubocop:enable Metrics/PerceivedComplexity
29
32
 
30
33
  def auto_correct(corrector, node)
31
34
  if parentheses_at_the_end_of_multiline_call?(node)
@@ -45,6 +48,16 @@ module RuboCop
45
48
  node.each_ancestor(:def, :defs).any?(&:endless?) && node.arguments.any?
46
49
  end
47
50
 
51
+ # Require hash value omission be enclosed in parentheses to prevent the following issue:
52
+ # https://bugs.ruby-lang.org/issues/18396.
53
+ def require_parentheses_for_hash_value_omission?(node)
54
+ return unless (last_argument = node.last_argument)
55
+
56
+ return false unless (right_sibling = node.right_sibling)
57
+
58
+ last_argument.hash_type? && last_argument.pairs.last&.value_omission? && right_sibling
59
+ end
60
+
48
61
  def syntax_like_method_call?(node)
49
62
  node.implicit_call? || node.operator_method?
50
63
  end
@@ -43,9 +43,11 @@ module RuboCop
43
43
  # NOTE: Parentheses are still allowed in cases where omitting them
44
44
  # results in ambiguous or syntactically incorrect code. For example,
45
45
  # parentheses are required around a method with arguments when inside an
46
- # endless method definition introduced in Ruby 3.0. Parentheses are also
46
+ # endless method definition introduced in Ruby 3.0. Parentheses are also
47
47
  # allowed when forwarding arguments with the triple-dot syntax introduced
48
48
  # in Ruby 2.7 as omitting them starts an endless range.
49
+ # And Ruby 3.1's hash omission syntax has a case that requires parentheses
50
+ # because the issue https://bugs.ruby-lang.org/issues/18396.
49
51
  #
50
52
  # @example EnforcedStyle: require_parentheses (default)
51
53
  #
@@ -6,8 +6,13 @@ 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.
9
+ # Regardless of style, parentheses are necessary for:
10
+ #
11
+ # 1. Endless methods
12
+ # 2. Argument lists containing a `forward-arg` (`...`)
13
+ # 3. Argument lists containing an anonymous block forwarding (`&`)
14
+ #
15
+ # Removing the parens would be a syntax error here.
11
16
  #
12
17
  # @example EnforcedStyle: require_parentheses (default)
13
18
  # # The `require_parentheses` style requires method definitions
@@ -121,21 +126,13 @@ module RuboCop
121
126
  corrector.remove(arg_node.loc.end)
122
127
  end
123
128
 
124
- def correct_definition(def_node, corrector)
125
- arguments_range = def_node.arguments.source_range
126
- args_with_space = range_with_surrounding_space(range: arguments_range, side: :left)
127
- leading_space = range_between(args_with_space.begin_pos, arguments_range.begin_pos)
128
- corrector.replace(leading_space, '(')
129
- corrector.insert_after(arguments_range, ')')
130
- end
131
-
132
129
  def forced_parentheses?(node)
133
130
  # Regardless of style, parentheses are necessary for:
134
131
  # 1. Endless methods
135
132
  # 2. Argument lists containing a `forward-arg` (`...`)
133
+ # 3. Argument lists containing an anonymous block forwarding (`&`)
136
134
  # Removing the parens would be a syntax error here.
137
-
138
- node.endless? || node.arguments.any?(&:forward_arg_type?)
135
+ node.endless? || node.arguments.any?(&:forward_arg_type?) || anonymous_block_arg?(node)
139
136
  end
140
137
 
141
138
  def require_parentheses?(args)
@@ -151,7 +148,8 @@ module RuboCop
151
148
  location = node.arguments.source_range
152
149
 
153
150
  add_offense(location, message: MSG_MISSING) do |corrector|
154
- correct_definition(node, corrector)
151
+ add_parentheses(node.arguments, corrector)
152
+
155
153
  unexpected_style_detected 'require_no_parentheses'
156
154
  end
157
155
  end
@@ -163,6 +161,12 @@ module RuboCop
163
161
  unexpected_style_detected 'require_parentheses'
164
162
  end
165
163
  end
164
+
165
+ def anonymous_block_arg?(node)
166
+ return false unless (last_argument = node.arguments.last)
167
+
168
+ last_argument.blockarg_type? && last_argument.name.nil?
169
+ end
166
170
  end
167
171
  end
168
172
  end
@@ -27,6 +27,11 @@ module RuboCop
27
27
  # # bad
28
28
  # 10_000_00 # typical representation of $10,000 in cents
29
29
  #
30
+ # @example AllowedNumbers: [3000]
31
+ #
32
+ # # good
33
+ # 3000 # You can specify allowed numbers. (e.g. port number)
34
+ #
30
35
  class NumericLiterals < Base
31
36
  include IntegerNode
32
37
  extend AutoCorrector
@@ -51,9 +56,9 @@ module RuboCop
51
56
 
52
57
  def check(node)
53
58
  int = integer_part(node)
54
-
55
59
  # TODO: handle non-decimal literals as well
56
60
  return if int.start_with?('0')
61
+ return if allowed_numbers.include?(int)
57
62
  return unless int.size >= min_digits
58
63
 
59
64
  case int
@@ -99,6 +104,10 @@ module RuboCop
99
104
  def min_digits
100
105
  cop_config['MinDigits']
101
106
  end
107
+
108
+ def allowed_numbers
109
+ cop_config.fetch('AllowedNumbers', []).map(&:to_s)
110
+ end
102
111
  end
103
112
  end
104
113
  end
@@ -45,7 +45,7 @@ module RuboCop
45
45
 
46
46
  message = message(node)
47
47
  add_offense(node, message: message) do |corrector|
48
- corrector.replace(node, replacement(node))
48
+ autocorrect(corrector, node)
49
49
  end
50
50
  end
51
51
 
@@ -55,55 +55,30 @@ module RuboCop
55
55
  format(MSG, keyword: node.keyword)
56
56
  end
57
57
 
58
- def replacement(node)
58
+ def autocorrect(corrector, node)
59
59
  if always_multiline? || cannot_replace_to_ternary?(node)
60
- multiline_replacement(node)
60
+ IfThenCorrector.new(node, indentation: indentation_width).call(corrector)
61
61
  else
62
- replaced_node = ternary_replacement(node)
63
- return replaced_node unless node.parent
64
- return "(#{replaced_node})" if %i[and or].include?(node.parent.type)
65
- return "(#{replaced_node})" if node.parent.send_type? && node.parent.operator_method?
66
-
67
- replaced_node
62
+ corrector.replace(node, ternary_correction(node))
68
63
  end
69
64
  end
70
65
 
71
- def always_multiline?
72
- @config.for_cop('Style/OneLineConditional')['AlwaysCorrectToMultiline']
73
- end
66
+ def ternary_correction(node)
67
+ replaced_node = ternary_replacement(node)
74
68
 
75
- def cannot_replace_to_ternary?(node)
76
- node.elsif_conditional?
77
- end
69
+ return replaced_node unless node.parent
70
+ return "(#{replaced_node})" if %i[and or].include?(node.parent.type)
71
+ return "(#{replaced_node})" if node.parent.send_type? && node.parent.operator_method?
78
72
 
79
- def multiline_replacement(node, indentation = nil)
80
- indentation = ' ' * node.source_range.column if indentation.nil?
81
- if_branch_source = node.if_branch&.source || 'nil'
82
- elsif_indentation = indentation if node.respond_to?(:elsif?) && node.elsif?
83
- if_branch = <<~RUBY
84
- #{elsif_indentation}#{node.keyword} #{node.condition.source}
85
- #{indentation}#{branch_body_indentation}#{if_branch_source}
86
- RUBY
87
- else_branch = else_branch_to_multiline(node.else_branch, indentation)
88
- if_branch + else_branch
73
+ replaced_node
89
74
  end
90
75
 
91
- def else_branch_to_multiline(else_branch, indentation)
92
- if else_branch.nil?
93
- 'end'
94
- elsif else_branch.if_type? && else_branch.elsif?
95
- multiline_replacement(else_branch, indentation)
96
- else
97
- <<~RUBY.chomp
98
- #{indentation}else
99
- #{indentation}#{branch_body_indentation}#{else_branch.source}
100
- #{indentation}end
101
- RUBY
102
- end
76
+ def always_multiline?
77
+ @config.for_cop('Style/OneLineConditional')['AlwaysCorrectToMultiline']
103
78
  end
104
79
 
105
- def branch_body_indentation
106
- ' ' * (@config.for_cop('Layout/IndentationWidth')['Width'] || 2)
80
+ def cannot_replace_to_ternary?(node)
81
+ node.elsif_conditional?
107
82
  end
108
83
 
109
84
  def ternary_replacement(node)
@@ -141,6 +116,10 @@ module RuboCop
141
116
 
142
117
  node.respond_to?(:arguments?) && node.arguments? && !node.parenthesized_call?
143
118
  end
119
+
120
+ def indentation_width
121
+ @config.for_cop('Layout/IndentationWidth')['Width']
122
+ end
144
123
  end
145
124
  end
146
125
  end
@@ -82,10 +82,20 @@ module RuboCop
82
82
  end
83
83
 
84
84
  def autocorrect_single_variable_interpolation(corrector, embedded_node, node)
85
- variable_loc = embedded_node.children.first.loc
86
- replacement = "#{variable_loc.expression.source}.to_s"
85
+ embedded_var = embedded_node.children.first
87
86
 
88
- corrector.replace(node, replacement)
87
+ source = if require_parentheses?(embedded_var)
88
+ receiver = range_between(
89
+ embedded_var.loc.expression.begin_pos, embedded_var.loc.selector.end_pos
90
+ )
91
+ arguments = embedded_var.arguments.map(&:source).join(', ')
92
+
93
+ "#{receiver.source}(#{arguments})"
94
+ else
95
+ embedded_var.source
96
+ end
97
+
98
+ corrector.replace(node, "#{source}.to_s")
89
99
  end
90
100
 
91
101
  def autocorrect_other(corrector, embedded_node, node)
@@ -97,6 +107,10 @@ module RuboCop
97
107
  corrector.replace(embedded_loc.begin, '(')
98
108
  corrector.replace(embedded_loc.end, ').to_s')
99
109
  end
110
+
111
+ def require_parentheses?(node)
112
+ node.send_type? && !node.arguments.count.zero? && !node.parenthesized_call?
113
+ end
100
114
  end
101
115
  end
102
116
  end
@@ -80,7 +80,11 @@ module RuboCop
80
80
  end
81
81
 
82
82
  def without_character_class(loc)
83
- loc.source[1..-2]
83
+ without_character_class = loc.source[1..-2]
84
+
85
+ # Adds `\` to prevent auto-correction that changes to an interpolated string when `[#]`.
86
+ # e.g. From `/[#]{0}/` to `/#{0}/`
87
+ loc.source == '[#]' ? "\\#{without_character_class}" : without_character_class
84
88
  end
85
89
 
86
90
  def whitespace_in_free_space_mode?(node, elem)
@@ -53,7 +53,7 @@ module RuboCop
53
53
  yield __FILE__ __LINE__ __ENCODING__].freeze
54
54
 
55
55
  def self.autocorrect_incompatible_with
56
- [ColonMethodCall]
56
+ [ColonMethodCall, Layout::DotPosition]
57
57
  end
58
58
 
59
59
  def initialize(config = nil, options = nil)
@@ -218,11 +218,7 @@ module RuboCop
218
218
  def find_matching_receiver_invocation(method_chain, checked_variable)
219
219
  return nil unless method_chain
220
220
 
221
- receiver = if method_chain.block_type?
222
- method_chain.send_node.receiver
223
- else
224
- method_chain.receiver
225
- end
221
+ receiver = method_chain.receiver
226
222
 
227
223
  return receiver if receiver == checked_variable
228
224