rubocop 1.73.1 → 1.75.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (127) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  3. data/config/default.yml +64 -10
  4. data/config/internal_affairs.yml +4 -0
  5. data/config/obsoletion.yml +3 -1
  6. data/lib/rubocop/cli.rb +1 -1
  7. data/lib/rubocop/config.rb +35 -6
  8. data/lib/rubocop/config_loader.rb +4 -1
  9. data/lib/rubocop/config_loader_resolver.rb +2 -1
  10. data/lib/rubocop/config_obsoletion/extracted_cop.rb +4 -3
  11. data/lib/rubocop/config_obsoletion/renamed_cop.rb +18 -3
  12. data/lib/rubocop/config_obsoletion.rb +46 -2
  13. data/lib/rubocop/config_validator.rb +1 -0
  14. data/lib/rubocop/cop/internal_affairs/example_description.rb +3 -1
  15. data/lib/rubocop/cop/internal_affairs/node_pattern_groups.rb +1 -1
  16. data/lib/rubocop/cop/internal_affairs/node_type_group.rb +91 -0
  17. data/lib/rubocop/cop/internal_affairs/redundant_described_class_as_subject.rb +6 -5
  18. data/lib/rubocop/cop/internal_affairs.rb +1 -0
  19. data/lib/rubocop/cop/layout/block_alignment.rb +1 -0
  20. data/lib/rubocop/cop/layout/block_end_newline.rb +1 -0
  21. data/lib/rubocop/cop/layout/else_alignment.rb +1 -1
  22. data/lib/rubocop/cop/layout/empty_line_between_defs.rb +1 -1
  23. data/lib/rubocop/cop/layout/empty_lines_around_access_modifier.rb +1 -0
  24. data/lib/rubocop/cop/layout/empty_lines_around_block_body.rb +1 -0
  25. data/lib/rubocop/cop/layout/indentation_width.rb +1 -0
  26. data/lib/rubocop/cop/layout/line_length.rb +5 -1
  27. data/lib/rubocop/cop/layout/multiline_block_layout.rb +1 -0
  28. data/lib/rubocop/cop/layout/multiline_method_parameter_line_breaks.rb +1 -0
  29. data/lib/rubocop/cop/layout/redundant_line_break.rb +9 -5
  30. data/lib/rubocop/cop/layout/rescue_ensure_alignment.rb +1 -1
  31. data/lib/rubocop/cop/layout/space_around_operators.rb +4 -1
  32. data/lib/rubocop/cop/layout/space_before_block_braces.rb +1 -0
  33. data/lib/rubocop/cop/layout/space_inside_block_braces.rb +1 -0
  34. data/lib/rubocop/cop/lint/debugger.rb +2 -2
  35. data/lib/rubocop/cop/lint/empty_conditional_body.rb +15 -70
  36. data/lib/rubocop/cop/lint/erb_new_arguments.rb +0 -6
  37. data/lib/rubocop/cop/lint/literal_as_condition.rb +4 -0
  38. data/lib/rubocop/cop/lint/non_local_exit_from_iterator.rb +2 -2
  39. data/lib/rubocop/cop/lint/raise_exception.rb +29 -10
  40. data/lib/rubocop/cop/lint/redundant_type_conversion.rb +9 -3
  41. data/lib/rubocop/cop/lint/redundant_with_index.rb +3 -0
  42. data/lib/rubocop/cop/lint/redundant_with_object.rb +3 -0
  43. data/lib/rubocop/cop/lint/return_in_void_context.rb +4 -11
  44. data/lib/rubocop/cop/lint/shadowing_outer_local_variable.rb +8 -1
  45. data/lib/rubocop/cop/lint/shared_mutable_default.rb +12 -1
  46. data/lib/rubocop/cop/lint/unexpected_block_arity.rb +2 -0
  47. data/lib/rubocop/cop/lint/unreachable_code.rb +1 -0
  48. data/lib/rubocop/cop/lint/unreachable_loop.rb +5 -5
  49. data/lib/rubocop/cop/lint/useless_access_modifier.rb +1 -0
  50. data/lib/rubocop/cop/lint/useless_constant_scoping.rb +2 -11
  51. data/lib/rubocop/cop/lint/void.rb +1 -0
  52. data/lib/rubocop/cop/metrics/block_length.rb +1 -0
  53. data/lib/rubocop/cop/metrics/method_length.rb +1 -0
  54. data/lib/rubocop/cop/metrics/utils/code_length_calculator.rb +1 -1
  55. data/lib/rubocop/cop/mixin/check_line_breakable.rb +2 -2
  56. data/lib/rubocop/cop/mixin/check_single_line_suitability.rb +1 -1
  57. data/lib/rubocop/cop/mixin/forbidden_identifiers.rb +20 -0
  58. data/lib/rubocop/cop/mixin/forbidden_pattern.rb +16 -0
  59. data/lib/rubocop/cop/mixin/method_complexity.rb +1 -0
  60. data/lib/rubocop/cop/mixin/range_help.rb +12 -0
  61. data/lib/rubocop/cop/mixin/target_ruby_version.rb +1 -1
  62. data/lib/rubocop/cop/naming/method_name.rb +64 -8
  63. data/lib/rubocop/cop/naming/variable_name.rb +6 -19
  64. data/lib/rubocop/cop/registry.rb +9 -6
  65. data/lib/rubocop/cop/style/array_intersect.rb +39 -28
  66. data/lib/rubocop/cop/style/block_delimiters.rb +2 -1
  67. data/lib/rubocop/cop/style/class_and_module_children.rb +29 -7
  68. data/lib/rubocop/cop/style/collection_methods.rb +1 -0
  69. data/lib/rubocop/cop/style/combinable_loops.rb +1 -0
  70. data/lib/rubocop/cop/style/commented_keyword.rb +9 -2
  71. data/lib/rubocop/cop/style/comparable_between.rb +75 -0
  72. data/lib/rubocop/cop/style/double_negation.rb +1 -1
  73. data/lib/rubocop/cop/style/expand_path_arguments.rb +2 -7
  74. data/lib/rubocop/cop/style/exponential_notation.rb +2 -2
  75. data/lib/rubocop/cop/style/for.rb +1 -0
  76. data/lib/rubocop/cop/style/format_string_token.rb +38 -11
  77. data/lib/rubocop/cop/style/guard_clause.rb +2 -1
  78. data/lib/rubocop/cop/style/hash_each_methods.rb +3 -2
  79. data/lib/rubocop/cop/style/hash_fetch_chain.rb +105 -0
  80. data/lib/rubocop/cop/style/if_inside_else.rb +10 -13
  81. data/lib/rubocop/cop/style/if_unless_modifier.rb +2 -2
  82. data/lib/rubocop/cop/style/inverse_methods.rb +9 -5
  83. data/lib/rubocop/cop/style/invertible_unless_condition.rb +2 -2
  84. data/lib/rubocop/cop/style/ip_addresses.rb +2 -2
  85. data/lib/rubocop/cop/style/it_block_parameter.rb +100 -0
  86. data/lib/rubocop/cop/style/keyword_parameters_order.rb +13 -7
  87. data/lib/rubocop/cop/style/lambda.rb +1 -0
  88. data/lib/rubocop/cop/style/map_into_array.rb +1 -0
  89. data/lib/rubocop/cop/style/method_call_with_args_parentheses/omit_parentheses.rb +3 -3
  90. data/lib/rubocop/cop/style/method_called_on_do_end_block.rb +1 -0
  91. data/lib/rubocop/cop/style/multiline_block_chain.rb +2 -1
  92. data/lib/rubocop/cop/style/multiline_method_signature.rb +1 -9
  93. data/lib/rubocop/cop/style/next.rb +44 -0
  94. data/lib/rubocop/cop/style/object_then.rb +1 -0
  95. data/lib/rubocop/cop/style/proc.rb +1 -0
  96. data/lib/rubocop/cop/style/raise_args.rb +8 -8
  97. data/lib/rubocop/cop/style/redundant_begin.rb +1 -0
  98. data/lib/rubocop/cop/style/redundant_condition.rb +2 -3
  99. data/lib/rubocop/cop/style/redundant_current_directory_in_path.rb +14 -4
  100. data/lib/rubocop/cop/style/redundant_format.rb +10 -3
  101. data/lib/rubocop/cop/style/redundant_freeze.rb +1 -1
  102. data/lib/rubocop/cop/style/redundant_parentheses.rb +2 -1
  103. data/lib/rubocop/cop/style/redundant_self.rb +1 -0
  104. data/lib/rubocop/cop/style/redundant_sort_by.rb +17 -1
  105. data/lib/rubocop/cop/style/rescue_modifier.rb +3 -0
  106. data/lib/rubocop/cop/style/select_by_regexp.rb +4 -1
  107. data/lib/rubocop/cop/style/single_line_do_end_block.rb +3 -1
  108. data/lib/rubocop/cop/style/sole_nested_conditional.rb +41 -106
  109. data/lib/rubocop/cop/style/symbol_proc.rb +2 -0
  110. data/lib/rubocop/cop/style/top_level_method_definition.rb +1 -0
  111. data/lib/rubocop/cop/utils/format_string.rb +5 -2
  112. data/lib/rubocop/cop/variable_force/scope.rb +1 -1
  113. data/lib/rubocop/cop/variable_force/variable.rb +1 -6
  114. data/lib/rubocop/cop/variable_force.rb +1 -1
  115. data/lib/rubocop/directive_comment.rb +1 -1
  116. data/lib/rubocop/ext/regexp_node.rb +0 -1
  117. data/lib/rubocop/lsp/runtime.rb +4 -4
  118. data/lib/rubocop/lsp/stdin_runner.rb +3 -1
  119. data/lib/rubocop/rspec/cop_helper.rb +4 -1
  120. data/lib/rubocop/rspec/shared_contexts.rb +20 -0
  121. data/lib/rubocop/rspec/support.rb +2 -0
  122. data/lib/rubocop/runner.rb +5 -1
  123. data/lib/rubocop/target_ruby.rb +1 -1
  124. data/lib/rubocop/version.rb +14 -7
  125. data/lib/rubocop.rb +5 -0
  126. data/lib/ruby_lsp/rubocop/runtime_adapter.rb +20 -2
  127. metadata +12 -6
@@ -0,0 +1,75 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Style
6
+ # Checks for logical comparison which can be replaced with `Comparable#between?`.
7
+ #
8
+ # NOTE: `Comparable#between?` is on average slightly slower than logical comparison,
9
+ # although the difference generally isn't observable. If you require maximum
10
+ # performance, consider using logical comparison.
11
+ #
12
+ # @example
13
+ #
14
+ # # bad
15
+ # x >= min && x <= max
16
+ #
17
+ # # bad
18
+ # x <= max && x >= min
19
+ #
20
+ # # good
21
+ # x.between?(min, max)
22
+ #
23
+ class ComparableBetween < Base
24
+ extend AutoCorrector
25
+
26
+ MSG = 'Prefer `%<prefer>s` over logical comparison.'
27
+
28
+ # @!method logical_comparison_between_by_min_first?(node)
29
+ def_node_matcher :logical_comparison_between_by_min_first?, <<~PATTERN
30
+ (and
31
+ (send
32
+ {$_value :>= $_min | $_min :<= $_value})
33
+ (send
34
+ {$_value :<= $_max | $_max :>= $_value}))
35
+ PATTERN
36
+
37
+ # @!method logical_comparison_between_by_max_first?(node)
38
+ def_node_matcher :logical_comparison_between_by_max_first?, <<~PATTERN
39
+ (and
40
+ (send
41
+ {$_value :<= $_max | $_max :>= $_value})
42
+ (send
43
+ {$_value :>= $_min | $_min :<= $_value}))
44
+ PATTERN
45
+
46
+ def on_and(node)
47
+ logical_comparison_between_by_min_first?(node) do |*args|
48
+ min_and_value, max_and_value = args.each_slice(2).to_a
49
+
50
+ register_offense(node, min_and_value, max_and_value)
51
+ end
52
+
53
+ logical_comparison_between_by_max_first?(node) do |*args|
54
+ max_and_value, min_and_value = args.each_slice(2).to_a
55
+
56
+ register_offense(node, min_and_value, max_and_value)
57
+ end
58
+ end
59
+
60
+ private
61
+
62
+ def register_offense(node, min_and_value, max_and_value)
63
+ value = (min_and_value & max_and_value).first
64
+ min = min_and_value.find { _1 != value }
65
+ max = max_and_value.find { _1 != value }
66
+
67
+ prefer = "#{value.source}.between?(#{min.source}, #{max.source})"
68
+ add_offense(node, message: format(MSG, prefer: prefer)) do |corrector|
69
+ corrector.replace(node, prefer)
70
+ end
71
+ end
72
+ end
73
+ end
74
+ end
75
+ end
@@ -109,7 +109,7 @@ module RuboCop
109
109
  end
110
110
 
111
111
  def define_method?(node)
112
- return false unless node.block_type?
112
+ return false unless node.any_block_type?
113
113
 
114
114
  child = node.child_nodes.first
115
115
  return false unless child.send_type?
@@ -137,11 +137,11 @@ module RuboCop
137
137
 
138
138
  case depth(stripped_current_path)
139
139
  when 0
140
- range = arguments_range(current_path)
140
+ range = arguments_range(current_path.parent)
141
141
 
142
142
  corrector.replace(range, '__FILE__')
143
143
  when 1
144
- range = arguments_range(current_path)
144
+ range = arguments_range(current_path.parent)
145
145
 
146
146
  corrector.replace(range, '__dir__')
147
147
  else
@@ -185,11 +185,6 @@ module RuboCop
185
185
  corrector.remove(node.loc.dot)
186
186
  corrector.remove(node.loc.selector)
187
187
  end
188
-
189
- def arguments_range(node)
190
- range_between(node.parent.first_argument.source_range.begin_pos,
191
- node.parent.last_argument.source_range.end_pos)
192
- end
193
188
  end
194
189
  end
195
190
  end
@@ -60,8 +60,8 @@ module RuboCop
60
60
  class ExponentialNotation < Base
61
61
  include ConfigurableEnforcedStyle
62
62
  MESSAGES = {
63
- scientific: 'Use a mantissa in [1, 10[.',
64
- engineering: 'Use an exponent divisible by 3 and a mantissa in [0.1, 1000[.',
63
+ scientific: 'Use a mantissa >= 1 and < 10.',
64
+ engineering: 'Use an exponent divisible by 3 and a mantissa >= 0.1 and < 1000.',
65
65
  integral: 'Use an integer as mantissa, without trailing zero.'
66
66
  }.freeze
67
67
 
@@ -77,6 +77,7 @@ module RuboCop
77
77
  end
78
78
 
79
79
  alias on_numblock on_block
80
+ alias on_itblock on_block
80
81
 
81
82
  private
82
83
 
@@ -3,16 +3,24 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Style
6
- # Use a consistent style for named format string tokens.
6
+ # Use a consistent style for tokens within a format string.
7
7
  #
8
- # NOTE: `unannotated` style cop only works for strings
9
- # which are passed as arguments to those methods:
10
- # `printf`, `sprintf`, `format`, `%`.
11
- # The reason is that _unannotated_ format is very similar
12
- # to encoded URLs or Date/Time formatting strings.
8
+ # By default, all strings are evaluated. In some cases, this may be undesirable,
9
+ # as they could be used as arguments to a method that does not consider
10
+ # them to be tokens, but rather other identifiers or just part of the string.
13
11
  #
14
- # This cop's allowed methods can be customized with `AllowedMethods`.
15
- # By default, there are no allowed methods.
12
+ # `AllowedMethods` or `AllowedPatterns` can be configured with in order to mark specific
13
+ # methods as always allowed, thereby avoiding an offense from the cop. By default, there
14
+ # are no allowed methods.
15
+ #
16
+ # Additionally, the cop can be made conservative by configuring it with
17
+ # `Mode: conservative` (default `aggressive`). In this mode, tokens (regardless
18
+ # of `EnforcedStyle`) are only considered if used in the format string argument to the
19
+ # methods `printf`, `sprintf`, `format` and `%`.
20
+ #
21
+ # NOTE: Tokens in the `unannotated` style (eg. `%s`) are always treated as if
22
+ # configured with `Conservative: true`. This is done in order to prevent false positives,
23
+ # because this format is very similar to encoded URLs or Date/Time formatting strings.
16
24
  #
17
25
  # @example EnforcedStyle: annotated (default)
18
26
  #
@@ -82,6 +90,20 @@ module RuboCop
82
90
  # # good
83
91
  # redirect('foo/%{bar_id}')
84
92
  #
93
+ # @example Mode: conservative, EnforcedStyle: annotated
94
+ # # In `conservative` mode, offenses are only registered for strings
95
+ # # given to a known formatting method.
96
+ #
97
+ # # good
98
+ # "%{greeting}"
99
+ # foo("%{greeting}")
100
+ #
101
+ # # bad
102
+ # format("%{greeting}", greeting: 'Hello')
103
+ # printf("%{greeting}", greeting: 'Hello')
104
+ # sprintf("%{greeting}", greeting: 'Hello')
105
+ # "%{greeting}" % { greeting: 'Hello' }
106
+ #
85
107
  class FormatStringToken < Base
86
108
  include ConfigurableEnforcedStyle
87
109
  include AllowedMethods
@@ -153,8 +175,9 @@ module RuboCop
153
175
  corrector.replace(token_range, correction)
154
176
  end
155
177
 
156
- def unannotated_format?(node, detected_style)
157
- detected_style == :unannotated && !format_string_in_typical_context?(node)
178
+ def allowed_string?(node, detected_style)
179
+ (detected_style == :unannotated || conservative?) &&
180
+ !format_string_in_typical_context?(node)
158
181
  end
159
182
 
160
183
  def message(detected_style)
@@ -203,7 +226,7 @@ module RuboCop
203
226
  def collect_detections(node)
204
227
  detections = []
205
228
  tokens(node) do |detected_sequence, token_range|
206
- unless unannotated_format?(node, detected_sequence.style)
229
+ unless allowed_string?(node, detected_sequence.style)
207
230
  detections << [detected_sequence, token_range]
208
231
  end
209
232
  end
@@ -222,6 +245,10 @@ module RuboCop
222
245
  def max_unannotated_placeholders_allowed
223
246
  cop_config['MaxUnannotatedPlaceholdersAllowed']
224
247
  end
248
+
249
+ def conservative?
250
+ cop_config.fetch('Mode', :aggressive).to_sym == :conservative
251
+ end
225
252
  end
226
253
  end
227
254
  end
@@ -135,6 +135,7 @@ module RuboCop
135
135
  on_def(node)
136
136
  end
137
137
  alias on_numblock on_block
138
+ alias on_itblock on_block
138
139
 
139
140
  def on_if(node)
140
141
  return if accepted_form?(node)
@@ -213,7 +214,7 @@ module RuboCop
213
214
  if_branch = node.if_branch
214
215
  else_branch = node.else_branch
215
216
 
216
- corrector.replace(node.loc.begin, "\n") if node.loc.begin&.is?('then')
217
+ corrector.replace(node.loc.begin, "\n") if node.then?
217
218
 
218
219
  if if_branch&.send_type? && heredoc?(if_branch.last_argument)
219
220
  autocorrect_heredoc_argument(corrector, node, if_branch, else_branch, guard)
@@ -74,6 +74,7 @@ module RuboCop
74
74
  check_unused_block_args(node, key, value)
75
75
  end
76
76
  alias on_numblock on_block
77
+ alias on_itblock on_block
77
78
 
78
79
  # rubocop:disable Metrics/AbcSize
79
80
  def check_unused_block_args(node, key, value)
@@ -128,8 +129,8 @@ module RuboCop
128
129
  lvar_sources = node.body.each_descendant(:lvar).map(&:source)
129
130
 
130
131
  if block_arg.mlhs_type?
131
- block_arg.each_descendant(:arg, :restarg).all? do |block_arg|
132
- lvar_sources.none?(block_arg.source.delete_prefix('*'))
132
+ block_arg.each_descendant(:arg, :restarg).all? do |descendant|
133
+ lvar_sources.none?(descendant.source.delete_prefix('*'))
133
134
  end
134
135
  else
135
136
  lvar_sources.none?(block_arg.source.delete_prefix('*'))
@@ -0,0 +1,105 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Style
6
+ # Use `Hash#dig` instead of chaining potentially null `fetch` calls.
7
+ #
8
+ # When `fetch(identifier, nil)` calls are chained on a hash, the expectation
9
+ # is that each step in the chain returns either `nil` or another hash,
10
+ # and in both cases, these can be simplified with a single call to `dig` with
11
+ # multiple arguments.
12
+ #
13
+ # If the 2nd parameter is `{}` or `Hash.new`, an offense will also be registered,
14
+ # as long as the final call in the chain is a nil value. If a non-nil value is given,
15
+ # the chain will not be registered as an offense, as the default value cannot be safely
16
+ # given with `dig`.
17
+ #
18
+ # NOTE: See `Style/DigChain` for replacing chains of `dig` calls with
19
+ # a single method call.
20
+ #
21
+ # @safety
22
+ # This cop is unsafe because it cannot be guaranteed that the receiver
23
+ # is a `Hash` or that `fetch` or `dig` have the expected standard implementation.
24
+ #
25
+ # @example
26
+ # # bad
27
+ # hash.fetch('foo', nil)&.fetch('bar', nil)
28
+ #
29
+ # # bad
30
+ # # earlier members of the chain can return `{}` as long as the final `fetch`
31
+ # # has `nil` as a default value
32
+ # hash.fetch('foo', {}).fetch('bar', nil)
33
+ #
34
+ # # good
35
+ # hash.dig('foo', 'bar')
36
+ #
37
+ # # ok - not handled by the cop since the final `fetch` value is non-nil
38
+ # hash.fetch('foo', {}).fetch('bar', {})
39
+ #
40
+ class HashFetchChain < Base
41
+ extend AutoCorrector
42
+ extend TargetRubyVersion
43
+ include IgnoredNode
44
+
45
+ MSG = 'Use `%<replacement>s` instead.'
46
+ RESTRICT_ON_SEND = %i[fetch].freeze
47
+
48
+ minimum_target_ruby_version 2.3
49
+
50
+ # @!method diggable?(node)
51
+ def_node_matcher :diggable?, <<~PATTERN
52
+ (call _ :fetch $_arg {nil (hash) (send (const {nil? cbase} :Hash) :new)})
53
+ PATTERN
54
+
55
+ def on_send(node)
56
+ return if ignored_node?(node)
57
+ return if last_fetch_non_nil?(node)
58
+
59
+ last_replaceable_node, arguments = inspect_chain(node)
60
+ return unless last_replaceable_node
61
+ return unless arguments.size > 1
62
+
63
+ range = last_replaceable_node.selector.join(node.loc.end)
64
+ replacement = replacement(arguments)
65
+ message = format(MSG, replacement: replacement)
66
+
67
+ add_offense(range, message: message) do |corrector|
68
+ corrector.replace(range, replacement)
69
+ end
70
+ end
71
+ alias on_csend on_send
72
+
73
+ private
74
+
75
+ def last_fetch_non_nil?(node)
76
+ # When chaining `fetch` methods, `fetch(x, {})` is acceptable within
77
+ # the chain, as long as the last method in the chain has a `nil`
78
+ # default value.
79
+
80
+ return false unless node.method?(:fetch)
81
+
82
+ !node.last_argument&.nil_type?
83
+ end
84
+
85
+ def inspect_chain(node)
86
+ arguments = []
87
+
88
+ while (arg = diggable?(node))
89
+ arguments.unshift(arg)
90
+ ignore_node(node)
91
+ last_replaceable_node = node
92
+ node = node.receiver
93
+ end
94
+
95
+ [last_replaceable_node, arguments]
96
+ end
97
+
98
+ def replacement(arguments)
99
+ values = arguments.map(&:source).join(', ')
100
+ "dig(#{values})"
101
+ end
102
+ end
103
+ end
104
+ end
105
+ end
@@ -97,34 +97,31 @@ module RuboCop
97
97
  else
98
98
  correct_to_elsif_from_if_inside_else_form(corrector, node, node.condition)
99
99
  end
100
-
101
- corrector.remove(range_by_whole_lines(find_end_range(node), include_final_newline: true))
102
- return unless (if_branch = node.if_branch)
103
-
104
- range = range_by_whole_lines(if_branch.source_range, include_final_newline: true)
105
- corrector.remove(range)
106
100
  end
107
101
 
108
102
  def correct_to_elsif_from_modifier_form(corrector, node)
109
- corrector.replace(node.parent.loc.else, <<~RUBY.chop)
110
- elsif #{node.condition.source}
111
- #{indent(node.if_branch)}#{node.if_branch.source}
112
- end
113
- RUBY
103
+ corrector.replace(node.parent.loc.else, "elsif #{node.condition.source}")
104
+
105
+ condition_range = range_between(
106
+ node.if_branch.source_range.end_pos, node.condition.source_range.end_pos
107
+ )
108
+ corrector.remove(condition_range)
114
109
  end
115
110
 
116
- def correct_to_elsif_from_if_inside_else_form(corrector, node, condition)
111
+ def correct_to_elsif_from_if_inside_else_form(corrector, node, condition) # rubocop:disable Metrics/AbcSize
117
112
  corrector.replace(node.parent.loc.else, "elsif #{condition.source}")
118
113
 
119
114
  if_condition_range = if_condition_range(node, condition)
120
115
 
121
116
  if (if_branch = node.if_branch)
122
- corrector.replace(if_condition_range, if_branch.source)
117
+ corrector.replace(if_condition_range, range_with_comments(if_branch).source)
118
+ corrector.remove(range_with_comments_and_lines(if_branch))
123
119
  else
124
120
  corrector.remove(range_by_whole_lines(if_condition_range, include_final_newline: true))
125
121
  end
126
122
 
127
123
  corrector.remove(condition)
124
+ corrector.remove(range_by_whole_lines(find_end_range(node), include_final_newline: true))
128
125
  end
129
126
 
130
127
  def then?(node)
@@ -164,8 +164,8 @@ module RuboCop
164
164
 
165
165
  def too_long_due_to_comment_after_modifier?(node, comment)
166
166
  source_length = processed_source.lines[node.first_line - 1].length
167
- source_length >= max_line_length &&
168
- source_length - comment.source_range.length <= max_line_length
167
+
168
+ max_line_length.between?(source_length - comment.source_range.length, source_length)
169
169
  end
170
170
 
171
171
  def allowed_patterns
@@ -46,6 +46,7 @@ module RuboCop
46
46
 
47
47
  MSG = 'Use `%<inverse>s` instead of inverting `%<method>s`.'
48
48
  CLASS_COMPARISON_METHODS = %i[<= >= < >].freeze
49
+ SAFE_NAVIGATION_INCOMPATIBLE_METHODS = (CLASS_COMPARISON_METHODS + %i[any? none?]).freeze
49
50
  EQUALITY_METHODS = %i[== != =~ !~ <= >= < >].freeze
50
51
  NEGATED_EQUALITY_METHODS = %i[!= !~].freeze
51
52
  CAMEL_CASE = /[A-Z]+[a-z]+/.freeze
@@ -77,7 +78,7 @@ module RuboCop
77
78
  def on_send(node)
78
79
  inverse_candidate?(node) do |method_call, lhs, method, rhs|
79
80
  return unless inverse_methods.key?(method)
80
- return if negated?(node) || relational_comparison_with_safe_navigation?(method_call)
81
+ return if negated?(node) || safe_navigation_incompatible?(method_call)
81
82
  return if part_of_ignored_node?(node)
82
83
  return if possible_class_hierarchy_check?(lhs, rhs, method)
83
84
 
@@ -105,6 +106,7 @@ module RuboCop
105
106
  end
106
107
 
107
108
  alias on_numblock on_block
109
+ alias on_itblock on_block
108
110
 
109
111
  private
110
112
 
@@ -154,10 +156,6 @@ module RuboCop
154
156
  node.parent.respond_to?(:method?) && node.parent.method?(:!)
155
157
  end
156
158
 
157
- def relational_comparison_with_safe_navigation?(node)
158
- node.csend_type? && CLASS_COMPARISON_METHODS.include?(node.method_name)
159
- end
160
-
161
159
  def not_to_receiver(node, method_call)
162
160
  node.loc.selector.begin.join(method_call.source_range.begin)
163
161
  end
@@ -166,6 +164,12 @@ module RuboCop
166
164
  method_call.source_range.end.join(node.source_range.end)
167
165
  end
168
166
 
167
+ def safe_navigation_incompatible?(node)
168
+ return false unless node.csend_type?
169
+
170
+ SAFE_NAVIGATION_INCOMPATIBLE_METHODS.include?(node.method_name)
171
+ end
172
+
169
173
  # When comparing classes, `!(Integer < Numeric)` is not the same as
170
174
  # `Integer > Numeric`.
171
175
  def possible_class_hierarchy_check?(lhs, rhs, method)
@@ -89,8 +89,8 @@ module RuboCop
89
89
 
90
90
  def inheritance_check?(node)
91
91
  argument = node.first_argument
92
- node.method?(:<) &&
93
- (argument.const_type? && argument.short_name.to_s.upcase != argument.short_name.to_s)
92
+ node.method?(:<) && argument.const_type? &&
93
+ argument.short_name.to_s.upcase != argument.short_name.to_s
94
94
  end
95
95
 
96
96
  def preferred_condition(node)
@@ -32,7 +32,7 @@ module RuboCop
32
32
 
33
33
  # To try to avoid doing two regex checks on every string,
34
34
  # shortcut out if the string does not look like an IP address
35
- return false unless could_be_ip?(contents)
35
+ return false unless potential_ip?(contents)
36
36
 
37
37
  ::Resolv::IPv4::Regex.match?(contents) || ::Resolv::IPv6::Regex.match?(contents)
38
38
  end
@@ -52,7 +52,7 @@ module RuboCop
52
52
  Array(allowed_addresses).map(&:downcase)
53
53
  end
54
54
 
55
- def could_be_ip?(str)
55
+ def potential_ip?(str)
56
56
  # If the string is too long, it can't be an IP
57
57
  return false if too_long?(str)
58
58
 
@@ -0,0 +1,100 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Style
6
+ # Checks for blocks with one argument where `it` block parameter can be used.
7
+ #
8
+ # It provides three `EnforcedStyle` options:
9
+ #
10
+ # 1. `only_numbered_parameters` (default) ... Detects only numbered block parameters.
11
+ # 2. `always` ... Always uses the `it` block parameter.
12
+ # 3. `disallow` ... Disallows the `it` block parameter.
13
+ #
14
+ # A single numbered parameter is detected when `only_numbered_parameters` or `always`.
15
+ #
16
+ # @example EnforcedStyle: only_numbered_parameters (default)
17
+ # # bad
18
+ # block { do_something(_1) }
19
+ #
20
+ # # good
21
+ # block { do_something(it) }
22
+ # block { |named_param| do_something(named_param) }
23
+ #
24
+ # @example EnforcedStyle: always
25
+ # # bad
26
+ # block { do_something(_1) }
27
+ # block { |named_param| do_something(named_param) }
28
+ #
29
+ # # good
30
+ # block { do_something(it) }
31
+ #
32
+ # @example EnforcedStyle: disallow
33
+ # # bad
34
+ # block { do_something(it) }
35
+ #
36
+ # # good
37
+ # block { do_something(_1) }
38
+ # block { |named_param| do_something(named_param) }
39
+ #
40
+ class ItBlockParameter < Base
41
+ include ConfigurableEnforcedStyle
42
+ extend TargetRubyVersion
43
+ extend AutoCorrector
44
+
45
+ MSG_USE_IT_BLOCK_PARAMETER = 'Use `it` block parameter.'
46
+ MSG_AVOID_IT_BLOCK_PARAMETER = 'Avoid using `it` block parameter.'
47
+
48
+ minimum_target_ruby_version 3.4
49
+
50
+ def on_block(node)
51
+ return unless style == :always
52
+ return unless node.arguments.one?
53
+
54
+ # `restarg`, `kwrestarg`, `blockarg` nodes can return early.
55
+ return unless node.first_argument.arg_type?
56
+
57
+ variables = find_block_variables(node, node.first_argument.source)
58
+
59
+ variables.each do |variable|
60
+ add_offense(variable, message: MSG_USE_IT_BLOCK_PARAMETER) do |corrector|
61
+ corrector.remove(node.arguments)
62
+ corrector.replace(variable, 'it')
63
+ end
64
+ end
65
+ end
66
+
67
+ def on_numblock(node)
68
+ return if style == :disallow
69
+ return unless node.children[1] == 1
70
+
71
+ variables = find_block_variables(node, '_1')
72
+
73
+ variables.each do |variable|
74
+ add_offense(variable, message: MSG_USE_IT_BLOCK_PARAMETER) do |corrector|
75
+ corrector.replace(variable, 'it')
76
+ end
77
+ end
78
+ end
79
+
80
+ def on_itblock(node)
81
+ return unless style == :disallow
82
+
83
+ variables = find_block_variables(node, 'it')
84
+
85
+ variables.each do |variable|
86
+ add_offense(variable, message: MSG_AVOID_IT_BLOCK_PARAMETER)
87
+ end
88
+ end
89
+
90
+ private
91
+
92
+ def find_block_variables(node, block_argument_name)
93
+ node.each_descendant(:lvar).select do |descendant|
94
+ descendant.source == block_argument_name
95
+ end
96
+ end
97
+ end
98
+ end
99
+ end
100
+ end
@@ -42,19 +42,25 @@ module RuboCop
42
42
  return if kwarg_nodes.empty?
43
43
 
44
44
  add_offense(node) do |corrector|
45
- if node.parent.find(&:kwoptarg_type?) == node
46
- corrector.insert_before(node, "#{kwarg_nodes.map(&:source).join(', ')}, ")
45
+ defining_node = node.each_ancestor(:def, :defs, :block).first
46
+ next if processed_source.contains_comment?(arguments_range(defining_node))
47
+ next unless node.parent.find(&:kwoptarg_type?) == node
47
48
 
48
- arguments = node.each_ancestor(:def, :defs, :block).first.arguments
49
- append_newline_to_last_kwoptarg(arguments, corrector) unless parentheses?(arguments)
50
-
51
- remove_kwargs(kwarg_nodes, corrector)
52
- end
49
+ autocorrect(corrector, node, defining_node, kwarg_nodes)
53
50
  end
54
51
  end
55
52
 
56
53
  private
57
54
 
55
+ def autocorrect(corrector, node, defining_node, kwarg_nodes)
56
+ corrector.insert_before(node, "#{kwarg_nodes.map(&:source).join(', ')}, ")
57
+
58
+ arguments = defining_node.arguments
59
+ append_newline_to_last_kwoptarg(arguments, corrector) unless parentheses?(arguments)
60
+
61
+ remove_kwargs(kwarg_nodes, corrector)
62
+ end
63
+
58
64
  def append_newline_to_last_kwoptarg(arguments, corrector)
59
65
  last_argument = arguments.last
60
66
  return if last_argument.type?(:kwrestarg, :blockarg)
@@ -77,6 +77,7 @@ module RuboCop
77
77
  end
78
78
  end
79
79
  alias on_numblock on_block
80
+ alias on_itblock on_block
80
81
 
81
82
  private
82
83
 
@@ -127,6 +127,7 @@ module RuboCop
127
127
  end
128
128
 
129
129
  alias on_numblock on_block
130
+ alias on_itblock on_block
130
131
 
131
132
  private
132
133
 
@@ -108,7 +108,7 @@ module RuboCop
108
108
  end
109
109
 
110
110
  def call_in_literals?(node)
111
- parent = node.parent&.block_type? ? node.parent.parent : node.parent
111
+ parent = node.parent&.any_block_type? ? node.parent.parent : node.parent
112
112
  return false unless parent
113
113
 
114
114
  parent.type?(:pair, :array, :range) ||
@@ -117,7 +117,7 @@ module RuboCop
117
117
  end
118
118
 
119
119
  def call_in_logical_operators?(node)
120
- parent = node.parent&.block_type? ? node.parent.parent : node.parent
120
+ parent = node.parent&.any_block_type? ? node.parent.parent : node.parent
121
121
  return false unless parent
122
122
 
123
123
  logical_operator?(parent) ||
@@ -153,7 +153,7 @@ module RuboCop
153
153
  end
154
154
 
155
155
  def call_in_argument_with_block?(node)
156
- parent = node.parent&.block_type? && node.parent.parent
156
+ parent = node.parent&.any_block_type? && node.parent.parent
157
157
  return false unless parent
158
158
 
159
159
  parent.type?(:call, :super, :yield)