rubocop 1.31.2 → 1.32.0

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