rubocop 1.31.2 → 1.32.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 +23 -0
  4. data/lib/rubocop/cli.rb +1 -0
  5. data/lib/rubocop/config.rb +1 -1
  6. data/lib/rubocop/cop/internal_affairs/useless_restrict_on_send.rb +7 -1
  7. data/lib/rubocop/cop/layout/line_continuation_leading_space.rb +57 -13
  8. data/lib/rubocop/cop/layout/line_length.rb +2 -0
  9. data/lib/rubocop/cop/layout/multiline_method_argument_line_breaks.rb +4 -1
  10. data/lib/rubocop/cop/layout/multiline_method_parameter_line_breaks.rb +45 -0
  11. data/lib/rubocop/cop/lint/ambiguous_block_association.rb +7 -0
  12. data/lib/rubocop/cop/lint/deprecated_class_methods.rb +10 -4
  13. data/lib/rubocop/cop/lint/non_atomic_file_operation.rb +55 -24
  14. data/lib/rubocop/cop/lint/number_conversion.rb +7 -1
  15. data/lib/rubocop/cop/lint/redundant_safe_navigation.rb +7 -0
  16. data/lib/rubocop/cop/lint/require_range_parentheses.rb +57 -0
  17. data/lib/rubocop/cop/lint/safe_navigation_chain.rb +35 -1
  18. data/lib/rubocop/cop/metrics/block_length.rb +1 -0
  19. data/lib/rubocop/cop/metrics/method_length.rb +1 -0
  20. data/lib/rubocop/cop/mixin/check_line_breakable.rb +4 -0
  21. data/lib/rubocop/cop/mixin/percent_array.rb +60 -1
  22. data/lib/rubocop/cop/naming/predicate_name.rb +8 -0
  23. data/lib/rubocop/cop/style/class_equality_comparison.rb +22 -0
  24. data/lib/rubocop/cop/style/empty_else.rb +37 -0
  25. data/lib/rubocop/cop/style/empty_heredoc.rb +59 -0
  26. data/lib/rubocop/cop/style/fetch_env_var.rb +10 -177
  27. data/lib/rubocop/cop/style/format_string_token.rb +6 -0
  28. data/lib/rubocop/cop/style/if_with_boolean_literal_branches.rb +2 -0
  29. data/lib/rubocop/cop/style/method_call_without_args_parentheses.rb +12 -0
  30. data/lib/rubocop/cop/style/nested_parenthesized_calls.rb +9 -0
  31. data/lib/rubocop/cop/style/numeric_predicate.rb +20 -6
  32. data/lib/rubocop/cop/style/semicolon.rb +27 -3
  33. data/lib/rubocop/cop/style/symbol_array.rb +2 -3
  34. data/lib/rubocop/cop/style/symbol_proc.rb +9 -1
  35. data/lib/rubocop/cop/style/top_level_method_definition.rb +2 -0
  36. data/lib/rubocop/cop/style/trivial_accessors.rb +3 -0
  37. data/lib/rubocop/cop/style/word_array.rb +2 -3
  38. data/lib/rubocop/options.rb +3 -6
  39. data/lib/rubocop/rspec/shared_contexts.rb +14 -14
  40. data/lib/rubocop/rspec/support.rb +14 -0
  41. data/lib/rubocop/runner.rb +4 -0
  42. data/lib/rubocop/server/client_command/base.rb +1 -1
  43. data/lib/rubocop/version.rb +1 -1
  44. data/lib/rubocop.rb +3 -0
  45. metadata +8 -5
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 998eb88b2df9c70d2b432d79cd43901b7e72d33714c2a1ac1e1ebf3f64d75b85
4
- data.tar.gz: 335fd1c15204af498ebee7200f2958902ccfd96923f9530b589517a857967a72
3
+ metadata.gz: 270faf06487f8a915ef6293944db136da6fb51ec09b5613be4b848a471c858bb
4
+ data.tar.gz: 1c61746959019453862c2e90b8d3250ce3f9815b1eae286f1fd5b73fb267a1cb
5
5
  SHA512:
6
- metadata.gz: ceda90af46b2317f03cc4a86ea8aba7c63a96f12d55d6bea533ae2537d68bdc75a647f2d2a1f5103c1799f2dda6ec0f923351891050915a237919df241d39e1e
7
- data.tar.gz: 8a0c5273cf612ca4717d619ce33b37dc2206beb4c6d247c3b501a67ec364861e18c24878b513c37b67e499084162cb70ab618e6e090f7876fda8da37c93d45d1
6
+ metadata.gz: 86966e7ff42d0f514a447c8313cc9bdc13020ec1d375e584dba8ebae5bb535a1238380a57d55dfd697c735b3f9259f59939563f4b504f08570304e4ec1c2c7af
7
+ data.tar.gz: ce35c98b8b51b8b59c6c2aeac880a0618067a68f7080a2e44408a7e283471b2b57f84e57c127144632b61eaa8edf6e4369d4702c2baa222cb3b37fad38606bd5
data/README.md CHANGED
@@ -53,7 +53,7 @@ To prevent an unwanted RuboCop update you might want to use a conservative versi
53
53
  in your `Gemfile`:
54
54
 
55
55
  ```rb
56
- gem 'rubocop', '~> 1.31', require: false
56
+ gem 'rubocop', '~> 1.32', require: false
57
57
  ```
58
58
 
59
59
  See [our versioning policy](https://docs.rubocop.org/rubocop/versioning.html) for further details.
data/config/default.yml CHANGED
@@ -977,6 +977,11 @@ Layout/LineContinuationLeadingSpace:
977
977
  AutoCorrect: false
978
978
  SafeAutoCorrect: false
979
979
  VersionAdded: '1.31'
980
+ VersionChanged: '1.32'
981
+ EnforcedStyle: trailing
982
+ SupportedStyles:
983
+ - leading
984
+ - trailing
980
985
 
981
986
  Layout/LineContinuationSpacing:
982
987
  Description: 'Checks the spacing in front of backslash in line continuations.'
@@ -1153,6 +1158,13 @@ Layout/MultilineMethodDefinitionBraceLayout:
1153
1158
  - new_line
1154
1159
  - same_line
1155
1160
 
1161
+ Layout/MultilineMethodParameterLineBreaks:
1162
+ Description: >-
1163
+ Checks that each parameter in a multi-line method definition
1164
+ starts on a separate line.
1165
+ Enabled: false
1166
+ VersionAdded: '1.32'
1167
+
1156
1168
  Layout/MultilineOperationIndentation:
1157
1169
  Description: >-
1158
1170
  Checks indentation of binary operations that span more than
@@ -2128,6 +2140,11 @@ Lint/RequireParentheses:
2128
2140
  Enabled: true
2129
2141
  VersionAdded: '0.18'
2130
2142
 
2143
+ Lint/RequireRangeParentheses:
2144
+ Description: 'Checks that a range literal is enclosed in parentheses when the end of the range is at a line break.'
2145
+ Enabled: pending
2146
+ VersionAdded: '1.32'
2147
+
2131
2148
  Lint/RequireRelativeSelfPath:
2132
2149
  Description: 'Checks for uses a file requiring itself with `require_relative`.'
2133
2150
  Enabled: pending
@@ -3495,6 +3512,12 @@ Style/EmptyElse:
3495
3512
  - empty
3496
3513
  - nil
3497
3514
  - both
3515
+ AllowComments: false
3516
+
3517
+ Style/EmptyHeredoc:
3518
+ Description: 'Checks for using empty heredoc to reduce redundancy.'
3519
+ Enabled: pending
3520
+ VersionAdded: '1.32'
3498
3521
 
3499
3522
  Style/EmptyLambdaParameter:
3500
3523
  Description: 'Omit parens for empty lambda parameters.'
data/lib/rubocop/cli.rb CHANGED
@@ -12,6 +12,7 @@ module RuboCop
12
12
  color debug display_style_guide display_time display_only_fail_level_offenses
13
13
  display_only_failed except extra_details fail_level fix_layout format
14
14
  ignore_disable_comments lint only only_guide_cops require safe
15
+ autocorrect safe_autocorrect autocorrect_all
15
16
  ].freeze
16
17
 
17
18
  class Finished < StandardError; end
@@ -25,7 +25,7 @@ module RuboCop
25
25
  @loaded_path = loaded_path
26
26
  @for_cop = Hash.new do |h, cop|
27
27
  qualified_cop_name = Cop::Registry.qualified_cop_name(cop, loaded_path)
28
- cop_options = self[qualified_cop_name] || {}
28
+ cop_options = self[qualified_cop_name].dup || {}
29
29
  cop_options['Enabled'] = enable_cop?(qualified_cop_name, cop_options)
30
30
  h[cop] = cop_options
31
31
  end
@@ -33,7 +33,13 @@ module RuboCop
33
33
  MSG = 'Useless `RESTRICT_ON_SEND` is defined.'
34
34
 
35
35
  # @!method defined_send_callback?(node)
36
- def_node_search :defined_send_callback?, '(def {:on_send :after_send} ...)'
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
37
43
 
38
44
  def on_casgn(node)
39
45
  return if !restrict_on_send?(node) || defined_send_callback?(node.parent)
@@ -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
@@ -7,6 +7,7 @@ module RuboCop
7
7
  # when param passed without parentheses.
8
8
  #
9
9
  # This cop can customize ignored methods with `IgnoredMethods`.
10
+ # By default, there are no methods to ignored.
10
11
  #
11
12
  # @example
12
13
  #
@@ -29,10 +30,16 @@ module RuboCop
29
30
  # # Lambda arguments require no disambiguation
30
31
  # foo = ->(bar) { bar.baz }
31
32
  #
33
+ # @example IgnoredMethods: [] (default)
34
+ #
35
+ # # bad
36
+ # expect { do_something }.to change { object.attribute }
37
+ #
32
38
  # @example IgnoredMethods: [change]
33
39
  #
34
40
  # # good
35
41
  # expect { do_something }.to change { object.attribute }
42
+ #
36
43
  class AmbiguousBlockAssociation < Base
37
44
  include IgnoredMethods
38
45
 
@@ -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
 
@@ -25,30 +25,37 @@ module RuboCop
25
25
  # to be strictly equivalent to that before the replacement.
26
26
  #
27
27
  # @example
28
- # # bad
29
- # unless FileTest.exist?(path)
30
- # FileUtils.makedirs(path)
28
+ # # bad - race condition with another process may result in an error in `mkdir`
29
+ # unless Dir.exist?(path)
30
+ # FileUtils.mkdir(path)
31
31
  # end
32
32
  #
33
- # if FileTest.exist?(path)
33
+ # # good - atomic and idempotent creation
34
+ # FileUtils.mkdir_p(path)
35
+ #
36
+ # # bad - race condition with another process may result in an error in `remove`
37
+ # if File.exist?(path)
34
38
  # FileUtils.remove(path)
35
39
  # end
36
40
  #
37
- # # good
38
- # FileUtils.mkdir_p(path)
39
- #
40
- # FileUtils.rm_rf(path)
41
+ # # good - atomic and idempotent removal
42
+ # FileUtils.rm_f(path)
41
43
  #
42
44
  class NonAtomicFileOperation < Base
43
45
  extend AutoCorrector
44
46
  include Alignment
45
47
  include RangeHelp
46
48
 
47
- MSG = 'Remove unnecessary existence checks `%<receiver>s.%<method_name>s`.'
48
- MAKE_METHODS = %i[makedirs mkdir mkdir_p mkpath].freeze
49
- REMOVE_METHODS = %i[remove remove_dir remove_entry remove_entry_secure delete unlink
50
- remove_file rm rm_f rm_r rm_rf rmdir rmtree safe_unlink].freeze
51
- RESTRICT_ON_SEND = (MAKE_METHODS + REMOVE_METHODS).freeze
49
+ MSG_REMOVE_FILE_EXIST_CHECK = 'Remove unnecessary existence check ' \
50
+ '`%<receiver>s.%<method_name>s`.'
51
+ MSG_CHANGE_FORCE_METHOD = 'Use atomic file operation method `FileUtils.%<method_name>s`.'
52
+ MAKE_FORCE_METHODS = %i[makedirs mkdir_p mkpath].freeze
53
+ MAKE_METHODS = %i[mkdir].freeze
54
+ REMOVE_FORCE_METHODS = %i[rm_f rm_rf].freeze
55
+ REMOVE_METHODS = %i[remove remove_dir remove_entry remove_entry_secure
56
+ delete unlink remove_file rm rmdir safe_unlink].freeze
57
+ RESTRICT_ON_SEND = (MAKE_METHODS + MAKE_FORCE_METHODS + REMOVE_METHODS +
58
+ REMOVE_FORCE_METHODS).freeze
52
59
 
53
60
  # @!method send_exist_node(node)
54
61
  def_node_search :send_exist_node, <<-PATTERN
@@ -71,10 +78,9 @@ module RuboCop
71
78
  PATTERN
72
79
 
73
80
  def on_send(node)
74
- return unless (parent = node.parent) && parent.if_type?
75
- return if allowable_use_with_if?(parent)
81
+ return unless if_node_child?(node)
76
82
  return if explicit_not_force?(node)
77
- return unless (exist_node = send_exist_node(parent).first)
83
+ return unless (exist_node = send_exist_node(node.parent).first)
78
84
  return unless exist_node.first_argument == node.first_argument
79
85
 
80
86
  register_offense(node, exist_node)
@@ -82,44 +88,69 @@ module RuboCop
82
88
 
83
89
  private
84
90
 
91
+ def if_node_child?(node)
92
+ return false unless (parent = node.parent)
93
+
94
+ parent.if_type? && !allowable_use_with_if?(parent)
95
+ end
96
+
85
97
  def allowable_use_with_if?(if_node)
86
98
  if_node.condition.and_type? || if_node.condition.or_type? || if_node.else_branch
87
99
  end
88
100
 
89
101
  def register_offense(node, exist_node)
102
+ unless force_method?(node)
103
+ add_offense(node,
104
+ message: format(MSG_CHANGE_FORCE_METHOD,
105
+ method_name: replacement_method(node)))
106
+ end
107
+
90
108
  range = range_between(node.parent.loc.keyword.begin_pos,
91
109
  exist_node.loc.expression.end_pos)
92
-
93
- add_offense(range, message: message(exist_node)) do |corrector|
110
+ add_offense(range, message: message_remove_file_exist_check(exist_node)) do |corrector|
94
111
  autocorrect(corrector, node, range)
95
112
  end
96
113
  end
97
114
 
98
- def message(node)
115
+ def message_remove_file_exist_check(node)
99
116
  receiver, method_name = receiver_and_method_name(node)
100
- format(MSG, receiver: receiver, method_name: method_name)
117
+ format(MSG_REMOVE_FILE_EXIST_CHECK, receiver: receiver, method_name: method_name)
101
118
  end
102
119
 
103
120
  def autocorrect(corrector, node, range)
104
121
  corrector.remove(range)
122
+ autocorrect_replace_method(corrector, node)
123
+ corrector.remove(node.parent.loc.end) if node.parent.multiline?
124
+ end
125
+
126
+ def autocorrect_replace_method(corrector, node)
127
+ return if force_method?(node)
128
+
105
129
  corrector.replace(node.child_nodes.first.loc.name, 'FileUtils')
106
130
  corrector.replace(node.loc.selector, replacement_method(node))
107
- corrector.remove(node.parent.loc.end) if node.parent.multiline?
108
131
  end
109
132
 
110
133
  def replacement_method(node)
111
- return node.method_name if force_option?(node)
112
-
113
134
  if MAKE_METHODS.include?(node.method_name)
114
135
  'mkdir_p'
115
136
  elsif REMOVE_METHODS.include?(node.method_name)
116
- 'rm_rf'
137
+ 'rm_f'
138
+ else
139
+ node.method_name
117
140
  end
118
141
  end
119
142
 
143
+ def force_method?(node)
144
+ force_method_name?(node) || force_option?(node)
145
+ end
146
+
120
147
  def force_option?(node)
121
148
  node.arguments.any? { |arg| force?(arg) }
122
149
  end
150
+
151
+ def force_method_name?(node)
152
+ (MAKE_FORCE_METHODS + REMOVE_FORCE_METHODS).include?(node.method_name)
153
+ end
123
154
  end
124
155
  end
125
156
  end
@@ -16,7 +16,8 @@ module RuboCop
16
16
  # NOTE: Some values cannot be converted properly using one of the `Kernel`
17
17
  # method (for instance, `Time` and `DateTime` values are allowed by this
18
18
  # cop by default). Similarly, Rails' duration methods do not work well
19
- # with `Integer()` and can be ignored with `IgnoredMethods`.
19
+ # with `Integer()` and can be ignored with `IgnoredMethods`. By default,
20
+ # there are no methods to ignored.
20
21
  #
21
22
  # @safety
22
23
  # Autocorrection is unsafe because it is not guaranteed that the
@@ -45,6 +46,11 @@ module RuboCop
45
46
  # foo.try { |i| Float(i) }
46
47
  # bar.send { |i| Complex(i) }
47
48
  #
49
+ # @example IgnoredMethods: [] (default)
50
+ #
51
+ # # bad
52
+ # 10.minutes.to_i
53
+ #
48
54
  # @example IgnoredMethods: [minutes]
49
55
  #
50
56
  # # good
@@ -35,6 +35,13 @@ module RuboCop
35
35
  # # good - without `&.` this will always return `true`
36
36
  # foo&.respond_to?(:to_a)
37
37
  #
38
+ # @example AllowedMethods: [foo?]
39
+ # # bad
40
+ # do_something if attrs&.foo?(:[])
41
+ #
42
+ # # good
43
+ # do_something if attrs&.bar?(:[])
44
+ #
38
45
  class RedundantSafeNavigation < Base
39
46
  include AllowedMethods
40
47
  include RangeHelp
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Lint
6
+ # Checks that a range literal is enclosed in parentheses when the end of the range is
7
+ # at a line break.
8
+ #
9
+ # NOTE: The following is maybe intended for `(42..)`. But, compatible is `42..do_something`.
10
+ # So, this cop does not provide autocorrection because it is left to user.
11
+ #
12
+ # [source,ruby]
13
+ # ----
14
+ # case condition
15
+ # when 42..
16
+ # do_something
17
+ # end
18
+ # ----
19
+ #
20
+ # @example
21
+ #
22
+ # # bad - Represents `(1..42)`, not endless range.
23
+ # 1..
24
+ # 42
25
+ #
26
+ # # good - It's incompatible, but your intentions when using endless range may be:
27
+ # (1..)
28
+ # 42
29
+ #
30
+ # # good
31
+ # 1..42
32
+ #
33
+ # # good
34
+ # (1..42)
35
+ #
36
+ # # good
37
+ # (1..
38
+ # 42)
39
+ #
40
+ class RequireRangeParentheses < Base
41
+ MSG = 'Wrap the endless range literal `%<range>s` to avoid precedence ambiguity.'
42
+
43
+ def on_irange(node)
44
+ return if node.parent&.begin_type?
45
+ return unless node.begin && node.end
46
+ return if same_line?(node.begin, node.end)
47
+
48
+ message = format(MSG, range: "#{node.begin.source}#{node.loc.operator.source}")
49
+
50
+ add_offense(node, message: message)
51
+ end
52
+
53
+ alias on_erange on_irange
54
+ end
55
+ end
56
+ end
57
+ end
@@ -25,6 +25,7 @@ module RuboCop
25
25
  # x&.foo || bar
26
26
  class SafeNavigationChain < Base
27
27
  include NilMethods
28
+ extend AutoCorrector
28
29
  extend TargetRubyVersion
29
30
 
30
31
  minimum_target_ruby_version 2.3
@@ -48,12 +49,45 @@ module RuboCop
48
49
  Parser::Source::Range.new(node.source_range.source_buffer,
49
50
  safe_nav.source_range.end_pos,
50
51
  method_chain.source_range.end_pos)
51
- add_offense(location)
52
+ add_offense(location) do |corrector|
53
+ autocorrect(corrector, offense_range: location, send_node: method_chain)
54
+ end
52
55
  end
53
56
  end
54
57
 
55
58
  private
56
59
 
60
+ # @param [Parser::Source::Range] offense_range
61
+ # @param [RuboCop::AST::SendNode] send_node
62
+ # @return [String]
63
+ def add_safe_navigation_operator(offense_range:, send_node:)
64
+ source = \
65
+ if send_node.method?(:[]) || send_node.method?(:[]=)
66
+ format(
67
+ '%<method_name>s(%<arguments>s)',
68
+ arguments: send_node.arguments.map(&:source).join(', '),
69
+ method_name: send_node.method_name
70
+ )
71
+ else
72
+ offense_range.source.dup
73
+ end
74
+ source.prepend('.') unless send_node.dot?
75
+ source.prepend('&')
76
+ end
77
+
78
+ # @param [RuboCop::Cop::Corrector] corrector
79
+ # @param [Parser::Source::Range] offense_range
80
+ # @param [RuboCop::AST::SendNode] send_node
81
+ def autocorrect(corrector, offense_range:, send_node:)
82
+ corrector.replace(
83
+ offense_range,
84
+ add_safe_navigation_operator(
85
+ offense_range: offense_range,
86
+ send_node: send_node
87
+ )
88
+ )
89
+ end
90
+
57
91
  def method_chain(node)
58
92
  chain = node
59
93
  chain = chain.parent if chain.send_type? && chain.parent&.call_type?
@@ -15,6 +15,7 @@ module RuboCop
15
15
  #
16
16
  # NOTE: The `ExcludedMethods` configuration is deprecated and only kept
17
17
  # for backwards compatibility. Please use `IgnoredMethods` instead.
18
+ # By default, there are no methods to ignored.
18
19
  #
19
20
  # @example CountAsOne: ['array', 'heredoc']
20
21
  #
@@ -13,6 +13,7 @@ module RuboCop
13
13
  #
14
14
  # NOTE: The `ExcludedMethods` configuration is deprecated and only kept
15
15
  # for backwards compatibility. Please use `IgnoredMethods` instead.
16
+ # By default, there are no methods to ignored.
16
17
  #
17
18
  # @example CountAsOne: ['array', 'heredoc']
18
19
  #