rubocop 1.31.1 → 1.33.0

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