rubocop 1.31.1 → 1.33.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 (84) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +2 -2
  3. data/config/default.yml +68 -16
  4. data/config/obsoletion.yml +23 -1
  5. data/lib/rubocop/cache_config.rb +29 -0
  6. data/lib/rubocop/cli/command/auto_genenerate_config.rb +2 -2
  7. data/lib/rubocop/cli/command/init_dotfile.rb +1 -1
  8. data/lib/rubocop/cli.rb +1 -0
  9. data/lib/rubocop/config.rb +1 -1
  10. data/lib/rubocop/config_finder.rb +68 -0
  11. data/lib/rubocop/config_loader.rb +5 -45
  12. data/lib/rubocop/config_loader_resolver.rb +1 -1
  13. data/lib/rubocop/config_obsoletion/changed_parameter.rb +5 -0
  14. data/lib/rubocop/config_obsoletion/parameter_rule.rb +4 -0
  15. data/lib/rubocop/config_obsoletion.rb +7 -2
  16. data/lib/rubocop/cop/base.rb +1 -1
  17. data/lib/rubocop/cop/generator.rb +4 -0
  18. data/lib/rubocop/cop/internal_affairs/useless_restrict_on_send.rb +60 -0
  19. data/lib/rubocop/cop/internal_affairs.rb +1 -0
  20. data/lib/rubocop/cop/layout/block_end_newline.rb +32 -5
  21. data/lib/rubocop/cop/layout/first_argument_indentation.rb +6 -1
  22. data/lib/rubocop/cop/layout/first_array_element_indentation.rb +4 -3
  23. data/lib/rubocop/cop/layout/first_hash_element_indentation.rb +3 -3
  24. data/lib/rubocop/cop/layout/line_continuation_leading_space.rb +57 -13
  25. data/lib/rubocop/cop/layout/line_length.rb +2 -0
  26. data/lib/rubocop/cop/layout/multiline_method_argument_line_breaks.rb +4 -1
  27. data/lib/rubocop/cop/layout/multiline_method_parameter_line_breaks.rb +45 -0
  28. data/lib/rubocop/cop/lint/ambiguous_block_association.rb +26 -6
  29. data/lib/rubocop/cop/lint/debugger.rb +11 -1
  30. data/lib/rubocop/cop/lint/deprecated_class_methods.rb +10 -4
  31. data/lib/rubocop/cop/lint/empty_conditional_body.rb +60 -1
  32. data/lib/rubocop/cop/lint/non_atomic_file_operation.rb +60 -25
  33. data/lib/rubocop/cop/lint/number_conversion.rb +28 -6
  34. data/lib/rubocop/cop/lint/redundant_safe_navigation.rb +7 -0
  35. data/lib/rubocop/cop/lint/require_range_parentheses.rb +57 -0
  36. data/lib/rubocop/cop/lint/safe_navigation_chain.rb +35 -1
  37. data/lib/rubocop/cop/metrics/abc_size.rb +3 -1
  38. data/lib/rubocop/cop/metrics/block_length.rb +6 -6
  39. data/lib/rubocop/cop/metrics/method_length.rb +8 -7
  40. data/lib/rubocop/cop/mixin/allowed_methods.rb +15 -1
  41. data/lib/rubocop/cop/mixin/allowed_pattern.rb +9 -1
  42. data/lib/rubocop/cop/mixin/check_line_breakable.rb +4 -0
  43. data/lib/rubocop/cop/mixin/comments_help.rb +5 -1
  44. data/lib/rubocop/cop/mixin/def_node.rb +2 -7
  45. data/lib/rubocop/cop/mixin/method_complexity.rb +4 -9
  46. data/lib/rubocop/cop/mixin/multiline_element_indentation.rb +6 -13
  47. data/lib/rubocop/cop/mixin/percent_array.rb +60 -1
  48. data/lib/rubocop/cop/naming/predicate_name.rb +30 -1
  49. data/lib/rubocop/cop/style/block_delimiters.rb +26 -7
  50. data/lib/rubocop/cop/style/class_and_module_children.rb +4 -4
  51. data/lib/rubocop/cop/style/class_equality_comparison.rb +50 -3
  52. data/lib/rubocop/cop/style/empty_else.rb +37 -0
  53. data/lib/rubocop/cop/style/empty_heredoc.rb +73 -0
  54. data/lib/rubocop/cop/style/fetch_env_var.rb +10 -177
  55. data/lib/rubocop/cop/style/format_string_token.rb +25 -6
  56. data/lib/rubocop/cop/style/hash_except.rb +0 -4
  57. data/lib/rubocop/cop/style/if_with_boolean_literal_branches.rb +2 -0
  58. data/lib/rubocop/cop/style/method_call_with_args_parentheses/require_parentheses.rb +5 -1
  59. data/lib/rubocop/cop/style/method_call_with_args_parentheses.rb +7 -7
  60. data/lib/rubocop/cop/style/method_call_without_args_parentheses.rb +19 -2
  61. data/lib/rubocop/cop/style/module_function.rb +2 -2
  62. data/lib/rubocop/cop/style/nested_parenthesized_calls.rb +9 -0
  63. data/lib/rubocop/cop/style/numeric_predicate.rb +43 -9
  64. data/lib/rubocop/cop/style/redundant_condition.rb +19 -4
  65. data/lib/rubocop/cop/style/redundant_parentheses.rb +2 -1
  66. data/lib/rubocop/cop/style/redundant_sort.rb +21 -6
  67. data/lib/rubocop/cop/style/semicolon.rb +27 -3
  68. data/lib/rubocop/cop/style/symbol_array.rb +2 -3
  69. data/lib/rubocop/cop/style/symbol_proc.rb +35 -7
  70. data/lib/rubocop/cop/style/trivial_accessors.rb +3 -0
  71. data/lib/rubocop/cop/style/word_array.rb +2 -3
  72. data/lib/rubocop/options.rb +3 -6
  73. data/lib/rubocop/rake_task.rb +5 -1
  74. data/lib/rubocop/result_cache.rb +22 -20
  75. data/lib/rubocop/rspec/shared_contexts.rb +14 -14
  76. data/lib/rubocop/rspec/support.rb +14 -0
  77. data/lib/rubocop/runner.rb +4 -0
  78. data/lib/rubocop/server/cache.rb +33 -1
  79. data/lib/rubocop/server/cli.rb +19 -2
  80. data/lib/rubocop/server/client_command/base.rb +1 -1
  81. data/lib/rubocop/version.rb +1 -1
  82. data/lib/rubocop.rb +4 -2
  83. metadata +12 -7
  84. data/lib/rubocop/cop/mixin/ignored_methods.rb +0 -52
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module InternalAffairs
6
+ # Check for useless `RESTRICT_ON_SEND`.
7
+ #
8
+ # @example
9
+ # # bad
10
+ # class FooCop
11
+ # RESTRICT_ON_SEND = %i[bad_method].freeze
12
+ # end
13
+ #
14
+ # # good
15
+ # class FooCop
16
+ # RESTRICT_ON_SEND = %i[bad_method].freeze
17
+ # def on_send(node)
18
+ # # ...
19
+ # end
20
+ # end
21
+ #
22
+ # # good
23
+ # class FooCop
24
+ # RESTRICT_ON_SEND = %i[bad_method].freeze
25
+ # def after_send(node)
26
+ # # ...
27
+ # end
28
+ # end
29
+ #
30
+ class UselessRestrictOnSend < Base
31
+ extend AutoCorrector
32
+
33
+ MSG = 'Useless `RESTRICT_ON_SEND` is defined.'
34
+
35
+ # @!method defined_send_callback?(node)
36
+ def_node_search :defined_send_callback?, <<~PATTERN
37
+ {
38
+ (def {:on_send :after_send} ...)
39
+ (alias (sym {:on_send :after_send}) _source ...)
40
+ (send nil? :alias_method {(sym {:on_send :after_send}) (str {"on_send" "after_send"})} _source ...)
41
+ }
42
+ PATTERN
43
+
44
+ def on_casgn(node)
45
+ return if !restrict_on_send?(node) || defined_send_callback?(node.parent)
46
+
47
+ add_offense(node) do |corrector|
48
+ corrector.remove(node)
49
+ end
50
+ end
51
+
52
+ private
53
+
54
+ def restrict_on_send?(node)
55
+ node.name == :RESTRICT_ON_SEND
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
@@ -20,3 +20,4 @@ require_relative 'internal_affairs/redundant_method_dispatch_node'
20
20
  require_relative 'internal_affairs/style_detected_api_use'
21
21
  require_relative 'internal_affairs/undefined_config'
22
22
  require_relative 'internal_affairs/useless_message_assertion'
23
+ require_relative 'internal_affairs/useless_restrict_on_send'
@@ -36,24 +36,51 @@ module RuboCop
36
36
  # If the end is on its own line, there is no offense
37
37
  return if begins_its_line?(node.loc.end)
38
38
 
39
- add_offense(node.loc.end, message: message(node)) do |corrector|
40
- corrector.replace(delimiter_range(node), "\n#{node.loc.end.source}#{offset(node)}")
41
- end
39
+ register_offense(node)
42
40
  end
43
41
 
44
42
  private
45
43
 
44
+ def register_offense(node)
45
+ add_offense(node.loc.end, message: message(node)) do |corrector|
46
+ offense_range = offense_range(node)
47
+ replacement = "\n#{offense_range.source.strip}"
48
+
49
+ if (heredoc = last_heredoc_argument(node.body))
50
+ corrector.remove(offense_range)
51
+ corrector.insert_after(heredoc.loc.heredoc_end, replacement)
52
+ else
53
+ corrector.replace(offense_range, replacement)
54
+ end
55
+ end
56
+ end
57
+
46
58
  def message(node)
47
59
  format(MSG, line: node.loc.end.line, column: node.loc.end.column + 1)
48
60
  end
49
61
 
50
- def delimiter_range(node)
62
+ def last_heredoc_argument(node)
63
+ return unless (arguments = node&.arguments)
64
+
65
+ heredoc = arguments.reverse.detect(&:heredoc?)
66
+ return heredoc if heredoc
67
+
68
+ last_heredoc_argument(node.children.first)
69
+ end
70
+
71
+ def offense_range(node)
51
72
  Parser::Source::Range.new(
52
73
  node.loc.expression.source_buffer,
53
74
  node.children.compact.last.loc.expression.end_pos,
54
- node.loc.expression.end_pos
75
+ end_of_method_chain(node).loc.expression.end_pos
55
76
  )
56
77
  end
78
+
79
+ def end_of_method_chain(node)
80
+ return node unless node.parent&.call_type?
81
+
82
+ end_of_method_chain(node.parent)
83
+ end
57
84
  end
58
85
  end
59
86
  end
@@ -153,7 +153,8 @@ module RuboCop
153
153
  MSG = 'Indent the first argument one step more than %<base>s.'
154
154
 
155
155
  def on_send(node)
156
- return if style != :consistent && enforce_first_argument_with_fixed_indentation?
156
+ return if style != :consistent && enforce_first_argument_with_fixed_indentation? &&
157
+ !enable_layout_first_method_argument_line_break?
157
158
  return if !node.arguments? || bare_operator?(node) || node.setter_method?
158
159
 
159
160
  indent = base_indentation(node) + configured_indentation_width
@@ -267,6 +268,10 @@ module RuboCop
267
268
  argument_alignment_config['EnforcedStyle'] == 'with_fixed_indentation'
268
269
  end
269
270
 
271
+ def enable_layout_first_method_argument_line_break?
272
+ config.for_cop('Layout/FirstMethodArgumentLineBreak')['Enabled']
273
+ end
274
+
270
275
  def argument_alignment_config
271
276
  config.for_cop('Layout/ArgumentAlignment')
272
277
  end
@@ -120,14 +120,15 @@ module RuboCop
120
120
  check_first(first_elem, left_bracket, left_parenthesis, 0)
121
121
  end
122
122
 
123
- check_right_bracket(array_node.loc.end, left_bracket, left_parenthesis)
123
+ check_right_bracket(array_node.loc.end, first_elem, left_bracket, left_parenthesis)
124
124
  end
125
125
 
126
- def check_right_bracket(right_bracket, left_bracket, left_parenthesis)
126
+ def check_right_bracket(right_bracket, first_elem, left_bracket, left_parenthesis)
127
127
  # if the right bracket is on the same line as the last value, accept
128
128
  return if /\S/.match?(right_bracket.source_line[0...right_bracket.column])
129
129
 
130
- expected_column, indent_base_type = indent_base(left_bracket, left_parenthesis)
130
+ expected_column, indent_base_type = indent_base(left_bracket, first_elem,
131
+ left_parenthesis)
131
132
  @column_delta = expected_column - right_bracket.column
132
133
  return if @column_delta.zero?
133
134
 
@@ -158,14 +158,14 @@ module RuboCop
158
158
  end
159
159
  end
160
160
 
161
- check_right_brace(hash_node.loc.end, left_brace, left_parenthesis)
161
+ check_right_brace(hash_node.loc.end, first_pair, left_brace, left_parenthesis)
162
162
  end
163
163
 
164
- def check_right_brace(right_brace, left_brace, left_parenthesis)
164
+ def check_right_brace(right_brace, first_pair, left_brace, left_parenthesis)
165
165
  # if the right brace is on the same line as the last value, accept
166
166
  return if /\S/.match?(right_brace.source_line[0...right_brace.column])
167
167
 
168
- expected_column, indent_base_type = indent_base(left_brace, left_parenthesis)
168
+ expected_column, indent_base_type = indent_base(left_brace, first_pair, left_parenthesis)
169
169
  @column_delta = expected_column - right_brace.column
170
170
  return if @column_delta.zero?
171
171
 
@@ -4,9 +4,10 @@ module RuboCop
4
4
  module Cop
5
5
  module Layout
6
6
  # Checks that strings broken over multiple lines (by a backslash) contain
7
- # trailing spaces instead of leading spaces.
7
+ # trailing spaces instead of leading spaces (default) or leading spaces
8
+ # instead of trailing spaces.
8
9
  #
9
- # @example
10
+ # @example EnforcedStyle: trailing (default)
10
11
  # # bad
11
12
  # 'this text contains a lot of' \
12
13
  # ' spaces'
@@ -23,18 +24,38 @@ module RuboCop
23
24
  # 'this text is too ' \
24
25
  # 'long'
25
26
  #
27
+ # @example EnforcedStyle: leading
28
+ # # bad
29
+ # 'this text contains a lot of ' \
30
+ # 'spaces'
31
+ #
32
+ # # good
33
+ # 'this text contains a lot of' \
34
+ # ' spaces'
35
+ #
36
+ # # bad
37
+ # 'this text is too ' \
38
+ # 'long'
39
+ #
40
+ # # good
41
+ # 'this text is too' \
42
+ # ' long'
26
43
  class LineContinuationLeadingSpace < Base
27
44
  include RangeHelp
28
45
 
29
- MSG = 'Move leading spaces to the end of previous line.'
30
-
31
46
  def on_dstr(node)
32
- range_start = node.loc.expression.begin_pos - node.loc.expression.column
47
+ end_of_first_line = node.loc.expression.begin_pos - node.loc.expression.column
33
48
 
34
49
  raw_lines(node).each_cons(2) do |raw_line_one, raw_line_two|
35
- range_start += raw_line_one.length
50
+ end_of_first_line += raw_line_one.length
36
51
 
37
- investigate(raw_line_one, raw_line_two, range_start)
52
+ next unless continuation?(raw_line_one)
53
+
54
+ if enforced_style_leading?
55
+ investigate_leading_style(raw_line_one, end_of_first_line)
56
+ else
57
+ investigate_trailing_style(raw_line_two, end_of_first_line)
58
+ end
38
59
  end
39
60
  end
40
61
 
@@ -44,24 +65,47 @@ module RuboCop
44
65
  processed_source.raw_source.lines[node.first_line - 1, line_range(node).size]
45
66
  end
46
67
 
47
- def investigate(first_line, second_line, range_start)
48
- return unless continuation?(first_line)
68
+ def investigate_leading_style(first_line, end_of_first_line)
69
+ matches = first_line.match(/(?<trailing_spaces>\s+)(?<ending>['"]\s*\\\n)/)
70
+ return if matches.nil?
49
71
 
50
- matches = second_line.match(/\A(?<indent>\s*['"])(?<leading_spaces>\s+)/)
72
+ add_offense(leading_offense_range(end_of_first_line, matches))
73
+ end
74
+
75
+ def investigate_trailing_style(second_line, end_of_first_line)
76
+ matches = second_line.match(/\A(?<beginning>\s*['"])(?<leading_spaces>\s+)/)
51
77
  return if matches.nil?
52
78
 
53
- add_offense(offense_range(range_start, matches))
79
+ add_offense(trailing_offense_range(end_of_first_line, matches))
54
80
  end
55
81
 
56
82
  def continuation?(line)
57
83
  line.end_with?("\\\n")
58
84
  end
59
85
 
60
- def offense_range(range_start, matches)
61
- begin_pos = range_start + matches[:indent].length
86
+ def leading_offense_range(end_of_first_line, matches)
87
+ end_pos = end_of_first_line - matches[:ending].length
88
+ begin_pos = end_pos - matches[:trailing_spaces].length
89
+ range_between(begin_pos, end_pos)
90
+ end
91
+
92
+ def trailing_offense_range(end_of_first_line, matches)
93
+ begin_pos = end_of_first_line + matches[:beginning].length
62
94
  end_pos = begin_pos + matches[:leading_spaces].length
63
95
  range_between(begin_pos, end_pos)
64
96
  end
97
+
98
+ def message(_range)
99
+ if enforced_style_leading?
100
+ 'Move trailing spaces to the start of next line.'
101
+ else
102
+ 'Move leading spaces to the end of previous line.'
103
+ end
104
+ end
105
+
106
+ def enforced_style_leading?
107
+ cop_config['EnforcedStyle'] == 'leading'
108
+ end
65
109
  end
66
110
  end
67
111
  end
@@ -37,6 +37,7 @@ module RuboCop
37
37
  # * MultilineHashBraceLayout
38
38
  # * MultilineHashKeyLineBreaks
39
39
  # * MultilineMethodArgumentLineBreaks
40
+ # * MultilineMethodParameterLineBreaks
40
41
  # * ParameterAlignment
41
42
  #
42
43
  # Together, these cops will pretty print hashes, arrays,
@@ -79,6 +80,7 @@ module RuboCop
79
80
  alias on_array on_potential_breakable_node
80
81
  alias on_hash on_potential_breakable_node
81
82
  alias on_send on_potential_breakable_node
83
+ alias on_def on_potential_breakable_node
82
84
 
83
85
  def on_new_investigation
84
86
  check_for_breakable_semicolons(processed_source)
@@ -6,7 +6,7 @@ module RuboCop
6
6
  # Ensures that each argument in a multi-line method call
7
7
  # starts on a separate line.
8
8
  #
9
- # NOTE: this cop does not move the first argument, if you want that to
9
+ # NOTE: This cop does not move the first argument, if you want that to
10
10
  # be on a separate line, see `Layout/FirstMethodArgumentLineBreak`.
11
11
  #
12
12
  # @example
@@ -22,6 +22,9 @@ module RuboCop
22
22
  # b,
23
23
  # c
24
24
  # )
25
+ #
26
+ # # good
27
+ # foo(a, b, c)
25
28
  class MultilineMethodArgumentLineBreaks < Base
26
29
  include MultilineElementLineBreaks
27
30
  extend AutoCorrector
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Layout
6
+ # Ensures that each parameter in a multi-line method definition
7
+ # starts on a separate line.
8
+ #
9
+ # NOTE: This cop does not move the first argument, if you want that to
10
+ # be on a separate line, see `Layout/FirstMethodParameterLineBreak`.
11
+ #
12
+ # @example
13
+ #
14
+ # # bad
15
+ # def foo(a, b,
16
+ # c
17
+ # )
18
+ # end
19
+ #
20
+ # # good
21
+ # def foo(
22
+ # a,
23
+ # b,
24
+ # c
25
+ # )
26
+ # end
27
+ #
28
+ # # good
29
+ # def foo(a, b, c)
30
+ # end
31
+ class MultilineMethodParameterLineBreaks < Base
32
+ include MultilineElementLineBreaks
33
+ extend AutoCorrector
34
+
35
+ MSG = 'Each parameter in a multi-line method definition must start on a separate line.'
36
+
37
+ def on_def(node)
38
+ return if node.arguments.empty?
39
+
40
+ check_line_breaks(node, node.arguments)
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -6,7 +6,8 @@ module RuboCop
6
6
  # Checks for ambiguous block association with method
7
7
  # when param passed without parentheses.
8
8
  #
9
- # This cop can customize ignored methods with `IgnoredMethods`.
9
+ # This cop can customize allowed methods with `AllowedMethods`.
10
+ # By default, there are no methods to allowed.
10
11
  #
11
12
  # @example
12
13
  #
@@ -29,12 +30,30 @@ module RuboCop
29
30
  # # Lambda arguments require no disambiguation
30
31
  # foo = ->(bar) { bar.baz }
31
32
  #
32
- # @example IgnoredMethods: [change]
33
+ # @example AllowedMethods: [] (default)
34
+ #
35
+ # # bad
36
+ # expect { do_something }.to change { object.attribute }
37
+ #
38
+ # @example AllowedMethods: [change]
33
39
  #
34
40
  # # good
35
41
  # expect { do_something }.to change { object.attribute }
42
+ #
43
+ # @example AllowedPatterns: [] (default)
44
+ #
45
+ # # bad
46
+ # expect { do_something }.to change { object.attribute }
47
+ #
48
+ # @example AllowedPatterns: [/change/]
49
+ #
50
+ # # good
51
+ # expect { do_something }.to change { object.attribute }
52
+ # expect { do_something }.to not_change { object.attribute }
53
+ #
36
54
  class AmbiguousBlockAssociation < Base
37
- include IgnoredMethods
55
+ include AllowedMethods
56
+ include AllowedPattern
38
57
 
39
58
  MSG = 'Parenthesize the param `%<param>s` to make sure that the ' \
40
59
  'block will be associated with the `%<method>s` method ' \
@@ -45,7 +64,7 @@ module RuboCop
45
64
 
46
65
  return unless ambiguous_block_association?(node)
47
66
  return if node.parenthesized? || node.last_argument.lambda? || node.last_argument.proc? ||
48
- allowed_method?(node)
67
+ allowed_method_pattern?(node)
49
68
 
50
69
  message = message(node)
51
70
 
@@ -59,9 +78,10 @@ module RuboCop
59
78
  send_node.last_argument.block_type? && !send_node.last_argument.send_node.arguments?
60
79
  end
61
80
 
62
- def allowed_method?(node)
81
+ def allowed_method_pattern?(node)
63
82
  node.assignment? || node.operator_method? || node.method?(:[]) ||
64
- ignored_method?(node.last_argument.send_node.source)
83
+ allowed_method?(node.last_argument.method_name) ||
84
+ matches_allowed_pattern?(node.last_argument.method_name)
65
85
  end
66
86
 
67
87
  def message(send_node)
@@ -15,9 +15,19 @@ module RuboCop
15
15
  # [source,yaml]
16
16
  # ----
17
17
  # Lint/Debugger:
18
- # WebConsole: ~
18
+ # DebuggerMethods:
19
+ # WebConsole: ~
19
20
  # ----
20
21
  #
22
+ # You can also add your own methods by adding a new category:
23
+ #
24
+ # [source,yaml]
25
+ # ----
26
+ # Lint/Debugger:
27
+ # DebuggerMethods:
28
+ # MyDebugger:
29
+ # MyDebugger.debug_this
30
+ # ----
21
31
  #
22
32
  # @example
23
33
  #
@@ -8,22 +8,22 @@ module RuboCop
8
8
  # @example
9
9
  #
10
10
  # # bad
11
- #
12
11
  # File.exists?(some_path)
13
12
  # Dir.exists?(some_path)
14
13
  # iterator?
15
14
  # ENV.freeze # Calling `Env.freeze` raises `TypeError` since Ruby 2.7.
15
+ # ENV.clone
16
+ # ENV.dup # Calling `Env.dup` raises `TypeError` since Ruby 3.1.
16
17
  # Socket.gethostbyname(host)
17
18
  # Socket.gethostbyaddr(host)
18
19
  #
19
- # @example
20
- #
21
20
  # # good
22
- #
23
21
  # File.exist?(some_path)
24
22
  # Dir.exist?(some_path)
25
23
  # block_given?
26
24
  # ENV # `ENV.freeze` cannot prohibit changes to environment variables.
25
+ # ENV.to_h
26
+ # ENV.to_h # `ENV.dup` cannot dup `ENV`, use `ENV.to_h` to get a copy of `ENV` as a hash.
27
27
  # Addrinfo.getaddrinfo(nodename, service)
28
28
  # Addrinfo.tcp(host, port).getnameinfo
29
29
  class DeprecatedClassMethods < Base
@@ -111,6 +111,12 @@ module RuboCop
111
111
  DeprecatedClassMethod.new(:freeze, class_constant: :ENV) =>
112
112
  Replacement.new(nil, class_constant: :ENV),
113
113
 
114
+ DeprecatedClassMethod.new(:clone, class_constant: :ENV) =>
115
+ Replacement.new(:to_h, class_constant: :ENV),
116
+
117
+ DeprecatedClassMethod.new(:dup, class_constant: :ENV) =>
118
+ Replacement.new(:to_h, class_constant: :ENV),
119
+
114
120
  DeprecatedClassMethod.new(:gethostbyaddr, class_constant: :Socket, correctable: false) =>
115
121
  Replacement.new(:getnameinfo, class_constant: :Addrinfo, instance_method: true),
116
122
 
@@ -4,6 +4,9 @@ module RuboCop
4
4
  module Cop
5
5
  module Lint
6
6
  # Checks for the presence of `if`, `elsif` and `unless` branches without a body.
7
+ #
8
+ # NOTE: empty `else` branches are handled by `Style/EmptyElse`.
9
+ #
7
10
  # @example
8
11
  # # bad
9
12
  # if condition
@@ -53,7 +56,9 @@ module RuboCop
53
56
  # end
54
57
  #
55
58
  class EmptyConditionalBody < Base
59
+ extend AutoCorrector
56
60
  include CommentsHelp
61
+ include RangeHelp
57
62
 
58
63
  MSG = 'Avoid `%<keyword>s` branches without a body.'
59
64
 
@@ -61,7 +66,61 @@ module RuboCop
61
66
  return if node.body
62
67
  return if cop_config['AllowComments'] && contains_comments?(node)
63
68
 
64
- add_offense(node, message: format(MSG, keyword: node.keyword))
69
+ add_offense(node, message: format(MSG, keyword: node.keyword)) do |corrector|
70
+ autocorrect(corrector, node)
71
+ end
72
+ end
73
+
74
+ private
75
+
76
+ def autocorrect(corrector, node)
77
+ remove_comments(corrector, node)
78
+ remove_empty_branch(corrector, node)
79
+ correct_other_branches(corrector, node)
80
+ end
81
+
82
+ def remove_comments(corrector, node)
83
+ comments_in_range(node).each do |comment|
84
+ range = range_by_whole_lines(comment.loc.expression, include_final_newline: true)
85
+ corrector.remove(range)
86
+ end
87
+ end
88
+
89
+ def remove_empty_branch(corrector, node)
90
+ corrector.remove(deletion_range(branch_range(node)))
91
+ end
92
+
93
+ def correct_other_branches(corrector, node)
94
+ return unless (node.if? || node.unless?) && node.else_branch
95
+
96
+ if node.else_branch.if_type?
97
+ # Replace an orphaned `elsif` with `if`
98
+ corrector.replace(node.else_branch.loc.keyword, 'if')
99
+ else
100
+ # Flip orphaned `else`
101
+ corrector.replace(node.loc.else, "#{node.inverse_keyword} #{node.condition.source}")
102
+ end
103
+ end
104
+
105
+ def branch_range(node)
106
+ if node.loc.else
107
+ node.source_range.with(end_pos: node.loc.else.begin_pos - 1)
108
+ else
109
+ node.source_range
110
+ end
111
+ end
112
+
113
+ def deletion_range(range)
114
+ # Collect a range between the start of the `if` node and the next relevant node,
115
+ # including final new line.
116
+ # Based on `RangeHelp#range_by_whole_lines` but allows the `if` to not start
117
+ # on the first column.
118
+ buffer = @processed_source.buffer
119
+
120
+ last_line = buffer.source_line(range.last_line)
121
+ end_offset = last_line.length - range.last_column + 1
122
+
123
+ range.adjust(end_pos: end_offset).intersect(buffer.source_range)
65
124
  end
66
125
  end
67
126
  end