rubocop 1.75.7 → 1.76.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 (37) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +19 -13
  3. data/config/default.yml +48 -5
  4. data/config/obsoletion.yml +6 -3
  5. data/lib/rubocop/cop/autocorrect_logic.rb +18 -10
  6. data/lib/rubocop/cop/bundler/ordered_gems.rb +1 -1
  7. data/lib/rubocop/cop/gemspec/duplicated_assignment.rb +2 -2
  8. data/lib/rubocop/cop/gemspec/ordered_dependencies.rb +1 -1
  9. data/lib/rubocop/cop/internal_affairs/node_pattern_groups.rb +1 -0
  10. data/lib/rubocop/cop/layout/class_structure.rb +35 -0
  11. data/lib/rubocop/cop/layout/empty_lines_around_access_modifier.rb +6 -2
  12. data/lib/rubocop/cop/layout/first_argument_indentation.rb +1 -1
  13. data/lib/rubocop/cop/lint/duplicate_methods.rb +1 -1
  14. data/lib/rubocop/cop/lint/empty_interpolation.rb +3 -1
  15. data/lib/rubocop/cop/lint/float_comparison.rb +27 -0
  16. data/lib/rubocop/cop/lint/identity_comparison.rb +19 -15
  17. data/lib/rubocop/cop/lint/literal_as_condition.rb +16 -24
  18. data/lib/rubocop/cop/lint/shadowing_outer_local_variable.rb +5 -0
  19. data/lib/rubocop/cop/lint/useless_default_value_argument.rb +87 -0
  20. data/lib/rubocop/cop/lint/useless_or.rb +98 -0
  21. data/lib/rubocop/cop/mixin/ordered_gem_node.rb +1 -1
  22. data/lib/rubocop/cop/naming/predicate_method.rb +216 -0
  23. data/lib/rubocop/cop/naming/{predicate_name.rb → predicate_prefix.rb} +2 -2
  24. data/lib/rubocop/cop/style/def_with_parentheses.rb +18 -5
  25. data/lib/rubocop/cop/style/empty_string_inside_interpolation.rb +100 -0
  26. data/lib/rubocop/cop/style/if_unless_modifier.rb +2 -4
  27. data/lib/rubocop/cop/style/it_block_parameter.rb +33 -14
  28. data/lib/rubocop/cop/style/map_to_hash.rb +11 -0
  29. data/lib/rubocop/cop/style/method_call_with_args_parentheses/omit_parentheses.rb +1 -1
  30. data/lib/rubocop/cop/style/redundant_array_flatten.rb +48 -0
  31. data/lib/rubocop/cop/style/redundant_format.rb +6 -1
  32. data/lib/rubocop/cop/style/redundant_parentheses.rb +16 -5
  33. data/lib/rubocop/cop/style/safe_navigation.rb +10 -7
  34. data/lib/rubocop/cop/team.rb +1 -1
  35. data/lib/rubocop/version.rb +1 -1
  36. data/lib/rubocop.rb +6 -1
  37. metadata +12 -7
@@ -0,0 +1,87 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Lint
6
+ # Checks for usage of method `fetch` or `Array.new` with default value argument
7
+ # and block. In such cases, block will always be used as default value.
8
+ #
9
+ # This cop emulates Ruby warning "block supersedes default value argument" which
10
+ # applies to `Array.new`, `Array#fetch`, `Hash#fetch`, `ENV.fetch` and
11
+ # `Thread#fetch`.
12
+ #
13
+ # @safety
14
+ # This cop is unsafe because the receiver could have nonstandard implementation
15
+ # of `fetch`, or be a class other than the one listed above.
16
+ #
17
+ # It is also unsafe because default value argument could have side effects:
18
+ #
19
+ # [source,ruby]
20
+ # ----
21
+ # def x(a) = puts "side effect"
22
+ # Array.new(5, x(1)) { 2 }
23
+ # ----
24
+ #
25
+ # so removing it would change behavior.
26
+ #
27
+ # @example
28
+ # # bad
29
+ # x.fetch(key, default_value) { block_value }
30
+ # Array.new(size, default_value) { block_value }
31
+ #
32
+ # # good
33
+ # x.fetch(key) { block_value }
34
+ # Array.new(size) { block_value }
35
+ #
36
+ # # also good - in case default value argument is desired instead
37
+ # x.fetch(key, default_value)
38
+ # Array.new(size, default_value)
39
+ #
40
+ # # good - keyword arguments aren't registered as offenses
41
+ # x.fetch(key, keyword: :arg) { block_value }
42
+ #
43
+ # @example AllowedReceivers: ['Rails.cache']
44
+ # # good
45
+ # Rails.cache.fetch(name, options) { block }
46
+ #
47
+ class UselessDefaultValueArgument < Base
48
+ include AllowedReceivers
49
+ extend AutoCorrector
50
+
51
+ MSG = 'Block supersedes default value argument.'
52
+
53
+ RESTRICT_ON_SEND = %i[fetch new].freeze
54
+
55
+ # @!method default_value_argument_and_block(node)
56
+ def_node_matcher :default_value_argument_and_block, <<~PATTERN
57
+ (any_block
58
+ {
59
+ (call _receiver :fetch $_key $_default_value)
60
+ (send (const _ :Array) :new $_size $_default_value)
61
+ }
62
+ _args
63
+ _block_body)
64
+ PATTERN
65
+
66
+ def on_send(node)
67
+ unless (prev_arg_node, default_value_node = default_value_argument_and_block(node.parent))
68
+ return
69
+ end
70
+ return if allowed_receiver?(node.receiver)
71
+ return if hash_without_braces?(default_value_node)
72
+
73
+ add_offense(default_value_node) do |corrector|
74
+ corrector.remove(prev_arg_node.source_range.end.join(default_value_node.source_range))
75
+ end
76
+ end
77
+ alias on_csend on_send
78
+
79
+ private
80
+
81
+ def hash_without_braces?(node)
82
+ node.hash_type? && !node.braces?
83
+ end
84
+ end
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,98 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Lint
6
+ # Checks for useless OR (`||` and `or`) expressions.
7
+ #
8
+ # Some methods always return a truthy value, even when called
9
+ # on `nil` (e.g. `nil.to_i` evaluates to `0`). Therefore, OR expressions
10
+ # appended after these methods will never evaluate.
11
+ #
12
+ # @example
13
+ #
14
+ # # bad
15
+ # x.to_a || fallback
16
+ # x.to_c || fallback
17
+ # x.to_d || fallback
18
+ # x.to_i || fallback
19
+ # x.to_f || fallback
20
+ # x.to_h || fallback
21
+ # x.to_r || fallback
22
+ # x.to_s || fallback
23
+ # x.to_sym || fallback
24
+ # x.intern || fallback
25
+ # x.inspect || fallback
26
+ # x.hash || fallback
27
+ # x.object_id || fallback
28
+ # x.__id__ || fallback
29
+ #
30
+ # x.to_s or fallback
31
+ #
32
+ # # good - if fallback is same as return value of method called on nil
33
+ # x.to_a # nil.to_a returns []
34
+ # x.to_c # nil.to_c returns (0+0i)
35
+ # x.to_d # nil.to_d returns 0.0
36
+ # x.to_i # nil.to_i returns 0
37
+ # x.to_f # nil.to_f returns 0.0
38
+ # x.to_h # nil.to_h returns {}
39
+ # x.to_r # nil.to_r returns (0/1)
40
+ # x.to_s # nil.to_s returns ''
41
+ # x.to_sym # nil.to_sym raises an error
42
+ # x.intern # nil.intern raises an error
43
+ # x.inspect # nil.inspect returns "nil"
44
+ # x.hash # nil.hash returns an Integer
45
+ # x.object_id # nil.object_id returns an Integer
46
+ # x.__id__ # nil.object_id returns an Integer
47
+ #
48
+ # # good - if the intention is not to call the method on nil
49
+ # x&.to_a || fallback
50
+ # x&.to_c || fallback
51
+ # x&.to_d || fallback
52
+ # x&.to_i || fallback
53
+ # x&.to_f || fallback
54
+ # x&.to_h || fallback
55
+ # x&.to_r || fallback
56
+ # x&.to_s || fallback
57
+ # x&.to_sym || fallback
58
+ # x&.intern || fallback
59
+ # x&.inspect || fallback
60
+ # x&.hash || fallback
61
+ # x&.object_id || fallback
62
+ # x&.__id__ || fallback
63
+ #
64
+ # x&.to_s or fallback
65
+ #
66
+ class UselessOr < Base
67
+ MSG = '`%<rhs>s` will never evaluate because `%<lhs>s` always returns a truthy value.'
68
+
69
+ TRUTHY_RETURN_VALUE_METHODS = Set[:to_a, :to_c, :to_d, :to_i, :to_f, :to_h, :to_r,
70
+ :to_s, :to_sym, :intern, :inspect, :hash, :object_id,
71
+ :__id__].freeze
72
+
73
+ # @!method truthy_return_value_method?(node)
74
+ def_node_matcher :truthy_return_value_method?, <<~PATTERN
75
+ (send _ %TRUTHY_RETURN_VALUE_METHODS)
76
+ PATTERN
77
+
78
+ def on_or(node)
79
+ if truthy_return_value_method?(node.lhs)
80
+ report_offense(node, node.lhs)
81
+ elsif truthy_return_value_method?(node.rhs)
82
+ parent = node.parent
83
+ parent = parent.parent if parent&.begin_type?
84
+
85
+ report_offense(parent, node.rhs) if parent&.or_type?
86
+ end
87
+ end
88
+
89
+ private
90
+
91
+ def report_offense(or_node, truthy_node)
92
+ add_offense(or_node.loc.operator.join(or_node.rhs.source_range),
93
+ message: format(MSG, lhs: truthy_node.source, rhs: or_node.rhs.source))
94
+ end
95
+ end
96
+ end
97
+ end
98
+ end
@@ -24,7 +24,7 @@ module RuboCop
24
24
  gem_canonical_name(string_a) < gem_canonical_name(string_b)
25
25
  end
26
26
 
27
- def consecutive_lines(previous, current)
27
+ def consecutive_lines?(previous, current)
28
28
  first_line = get_source_range(current, treat_comments_as_separators).first_line
29
29
  previous.source_range.last_line == first_line - 1
30
30
  end
@@ -0,0 +1,216 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Naming
6
+ # Checks that predicate methods end with `?` and non-predicate methods do not.
7
+ #
8
+ # The names of predicate methods (methods that return a boolean value) should end
9
+ # in a question mark. Methods that don’t return a boolean, shouldn’t
10
+ # end in a question mark.
11
+ #
12
+ # The cop assesses a predicate method as one that returns boolean values. Likewise,
13
+ # a method that only returns literal values is assessed as non-predicate. The cop does
14
+ # not make an assessment if the return type is unknown (method calls, variables, etc.).
15
+ #
16
+ # NOTE: Operator methods (`def ==`, etc.) are ignored.
17
+ #
18
+ # By default, the cop runs in `conservative` mode, which allows a method to be named
19
+ # with a question mark as long as at least one return value is boolean. In `aggressive`
20
+ # mode, methods with a question mark will register an offense if any known non-boolean
21
+ # return values are detected.
22
+ #
23
+ # The cop also has `AllowedMethods` configuration in order to prevent the cop from
24
+ # registering an offense from a method name that does not confirm to the naming
25
+ # guidelines. By default, `call` is allowed.
26
+ #
27
+ # @example Mode: conservative (default)
28
+ # # bad
29
+ # def foo
30
+ # bar == baz
31
+ # end
32
+ #
33
+ # # good
34
+ # def foo?
35
+ # bar == baz
36
+ # end
37
+ #
38
+ # # bad
39
+ # def foo?
40
+ # 5
41
+ # end
42
+ #
43
+ # # good
44
+ # def foo
45
+ # 5
46
+ # end
47
+ #
48
+ # # good - operator method
49
+ # def ==(other)
50
+ # hash == other.hash
51
+ # end
52
+ #
53
+ # # good - at least one return value is boolean
54
+ # def foo?
55
+ # return unless bar?
56
+ # true
57
+ # end
58
+ #
59
+ # # ok - return type is not known
60
+ # def foo?
61
+ # bar
62
+ # end
63
+ #
64
+ # # ok - return type is not known
65
+ # def foo
66
+ # bar?
67
+ # end
68
+ #
69
+ # @example Mode: aggressive
70
+ # # bad - the method returns nil in some cases
71
+ # def foo?
72
+ # return unless bar?
73
+ # true
74
+ # end
75
+ #
76
+ class PredicateMethod < Base
77
+ include AllowedMethods
78
+
79
+ MSG_PREDICATE = 'Predicate method names should end with `?`.'
80
+ MSG_NON_PREDICATE = 'Non-predicate method names should not end with `?`.'
81
+
82
+ def on_def(node)
83
+ return if allowed?(node)
84
+
85
+ return_values = return_values(node.body)
86
+ return if acceptable?(return_values)
87
+
88
+ if node.predicate_method? && potential_non_predicate?(return_values)
89
+ add_offense(node.loc.name, message: MSG_NON_PREDICATE)
90
+ elsif !node.predicate_method? && all_return_values_boolean?(return_values)
91
+ add_offense(node.loc.name, message: MSG_PREDICATE)
92
+ end
93
+ end
94
+ alias on_defs on_def
95
+
96
+ private
97
+
98
+ def allowed?(node)
99
+ allowed_method?(node.method_name) ||
100
+ node.operator_method? ||
101
+ node.body.nil?
102
+ end
103
+
104
+ def acceptable?(return_values)
105
+ # In `conservative` mode, if the method returns `super`, `zsuper`, or a
106
+ # non-comparison method call, the method name is acceptable.
107
+ return false unless conservative?
108
+
109
+ return_values.any? do |value|
110
+ value.type?(:super, :zsuper) || non_comparison_call?(value)
111
+ end
112
+ end
113
+
114
+ def non_comparison_call?(value)
115
+ value.call_type? && !value.comparison_method?
116
+ end
117
+
118
+ def return_values(node)
119
+ # Collect all the (implicit and explicit) return values of a node
120
+ return_values = Set.new(node.begin_type? ? [] : [extract_return_value(node)])
121
+
122
+ node.each_descendant(:return) do |return_node|
123
+ return_values << extract_return_value(return_node)
124
+ end
125
+
126
+ last_value = last_value(node)
127
+ return_values << last_value if last_value
128
+
129
+ process_return_values(return_values)
130
+ end
131
+
132
+ def all_return_values_boolean?(return_values)
133
+ values = return_values.reject { |value| value.type?(:super, :zsuper) }
134
+ return false if values.empty?
135
+
136
+ values.all? { |value| boolean_return?(value) }
137
+ end
138
+
139
+ def boolean_return?(value)
140
+ value.boolean_type? || (value.call_type? && value.comparison_method?)
141
+ end
142
+
143
+ def potential_non_predicate?(return_values)
144
+ # Assumes a method to be non-predicate if all return values are non-boolean literals.
145
+ #
146
+ # In `Mode: conservative`, if any of the return values is a boolean,
147
+ # the method name is acceptable.
148
+ # In `Mode: aggressive`, all return values must be booleans for a predicate
149
+ # method, or else an offense will be registered.
150
+ return false if conservative? && return_values.any? { |value| boolean_return?(value) }
151
+
152
+ return_values.any? do |value|
153
+ value.literal? && !value.boolean_type?
154
+ end
155
+ end
156
+
157
+ def extract_return_value(node)
158
+ return node unless node.return_type?
159
+
160
+ # `return` without a value is a `nil` return.
161
+ return s(:nil) if node.arguments.empty?
162
+
163
+ # When there's a multiple return, it cannot be a predicate
164
+ # so just return an `array` sexp for simplicity.
165
+ return s(:array) unless node.arguments.one?
166
+
167
+ node.first_argument
168
+ end
169
+
170
+ def last_value(node)
171
+ value = node.begin_type? ? node.children.last : node
172
+ value.return_type? ? extract_return_value(value) : value
173
+ end
174
+
175
+ def process_return_values(return_values)
176
+ return_values.flat_map do |value|
177
+ if value.conditional?
178
+ process_return_values(extract_conditional_branches(value))
179
+ elsif and_or?(value)
180
+ process_return_values(extract_and_or_clauses(value))
181
+ else
182
+ value
183
+ end
184
+ end
185
+ end
186
+
187
+ def and_or?(node)
188
+ node.type?(:and, :or)
189
+ end
190
+
191
+ def extract_and_or_clauses(node)
192
+ # Recursively traverse an `and` or `or` node to collect all clauses within
193
+ return node unless and_or?(node)
194
+
195
+ [extract_and_or_clauses(node.lhs), extract_and_or_clauses(node.rhs)].flatten
196
+ end
197
+
198
+ def extract_conditional_branches(node)
199
+ return node unless node.conditional?
200
+
201
+ if node.type?(:while, :until)
202
+ # If there is no body, act as implicit `nil`.
203
+ node.body ? [last_value(node.body)] : [s(:nil)]
204
+ else
205
+ # Branches with no value act as an implicit `nil`.
206
+ node.branches.filter_map { |branch| branch ? last_value(branch) : s(:nil) }
207
+ end
208
+ end
209
+
210
+ def conservative?
211
+ cop_config.fetch('Mode', :conservative).to_sym == :conservative
212
+ end
213
+ end
214
+ end
215
+ end
216
+ end
@@ -100,7 +100,7 @@ module RuboCop
100
100
  # # good
101
101
  # def_node_matcher(:even?) { |value| }
102
102
  #
103
- class PredicateName < Base
103
+ class PredicatePrefix < Base
104
104
  include AllowedMethods
105
105
 
106
106
  # @!method dynamic_method_define(node)
@@ -143,7 +143,7 @@ module RuboCop
143
143
  next if predicate_prefixes.include?(forbidden_prefix)
144
144
 
145
145
  raise ValidationError, <<~MSG.chomp
146
- The `Naming/PredicateName` cop is misconfigured. Prefix #{forbidden_prefix} must be included in NamePrefix because it is included in ForbiddenPrefixes.
146
+ The `Naming/PredicatePrefix` cop is misconfigured. Prefix #{forbidden_prefix} must be included in NamePrefix because it is included in ForbiddenPrefixes.
147
147
  MSG
148
148
  end
149
149
  end
@@ -25,8 +25,9 @@ module RuboCop
25
25
  # # good
26
26
  # def foo = do_something
27
27
  #
28
- # # good (without parentheses it's a syntax error)
28
+ # # good - without parentheses it's a syntax error
29
29
  # def foo() do_something end
30
+ # def foo()=do_something
30
31
  #
31
32
  # # bad
32
33
  # def Baz.foo()
@@ -38,19 +39,31 @@ module RuboCop
38
39
  # do_something
39
40
  # end
40
41
  class DefWithParentheses < Base
42
+ include RangeHelp
41
43
  extend AutoCorrector
42
44
 
43
45
  MSG = "Omit the parentheses in defs when the method doesn't accept any arguments."
44
46
 
45
47
  def on_def(node)
46
- return if node.single_line? && !node.endless?
47
- return unless !node.arguments? && (node_arguments = node.arguments.source_range)
48
+ return unless !node.arguments? && (arguments_range = node.arguments.source_range)
49
+ return if parentheses_required?(node, arguments_range)
48
50
 
49
- add_offense(node_arguments) do |corrector|
50
- corrector.remove(node_arguments)
51
+ add_offense(arguments_range) do |corrector|
52
+ corrector.remove(arguments_range)
51
53
  end
52
54
  end
53
55
  alias on_defs on_def
56
+
57
+ private
58
+
59
+ def parentheses_required?(node, arguments_range)
60
+ return true if node.single_line? && !node.endless?
61
+
62
+ end_pos = arguments_range.end.end_pos
63
+ token_after_argument = range_between(end_pos, end_pos + 1).source
64
+
65
+ token_after_argument == '='
66
+ end
54
67
  end
55
68
  end
56
69
  end
@@ -0,0 +1,100 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Style
6
+ # Checks for empty strings being assigned inside string interpolation.
7
+ #
8
+ # Empty strings are a meaningless outcome inside of string interpolation, so we remove them.
9
+ # Alternatively, when configured to do so, we prioritise using empty strings.
10
+ #
11
+ # While this cop would also apply to variables that are only going to be used as strings,
12
+ # RuboCop can't detect that, so we only check inside of string interpolation.
13
+ #
14
+ # @example EnforcedStyle: trailing_conditional (default)
15
+ # # bad
16
+ # "#{condition ? 'foo' : ''}"
17
+ #
18
+ # # good
19
+ # "#{'foo' if condition}"
20
+ #
21
+ # # bad
22
+ # "#{condition ? '' : 'foo'}"
23
+ #
24
+ # # good
25
+ # "#{'foo' unless condition}"
26
+ #
27
+ # @example EnforcedStyle: ternary
28
+ # # bad
29
+ # "#{'foo' if condition}"
30
+ #
31
+ # # good
32
+ # "#{condition ? 'foo' : ''}"
33
+ #
34
+ # # bad
35
+ # "#{'foo' unless condition}"
36
+ #
37
+ # # good
38
+ # "#{condition ? '' : 'foo'}"
39
+ #
40
+ class EmptyStringInsideInterpolation < Base
41
+ include ConfigurableEnforcedStyle
42
+ include Interpolation
43
+ extend AutoCorrector
44
+
45
+ MSG_TRAILING_CONDITIONAL = 'Do not use trailing conditionals in string interpolation.'
46
+ MSG_TERNARY = 'Do not return empty strings in string interpolation.'
47
+
48
+ # rubocop:disable Metrics/AbcSize, Metrics/MethodLength, Metrics/PerceivedComplexity
49
+ def on_interpolation(node)
50
+ node.each_child_node(:if) do |child_node|
51
+ if style == :trailing_conditional
52
+ if empty_if_outcome?(child_node)
53
+ ternary_style_autocorrect(child_node, child_node.else_branch.source, 'unless')
54
+ end
55
+
56
+ if empty_else_outcome?(child_node)
57
+ ternary_style_autocorrect(child_node, child_node.if_branch.source, 'if')
58
+ end
59
+ elsif style == :ternary
60
+ next unless child_node.modifier_form?
61
+
62
+ ternary_component = if child_node.unless?
63
+ "'' : #{child_node.if_branch.source}"
64
+ else
65
+ "#{child_node.if_branch.source} : ''"
66
+ end
67
+
68
+ add_offense(node, message: MSG_TRAILING_CONDITIONAL) do |corrector|
69
+ corrector.replace(node, "\#{#{child_node.condition.source} ? #{ternary_component}}")
70
+ end
71
+ end
72
+ end
73
+ end
74
+ # rubocop:enable Metrics/AbcSize, Metrics/MethodLength, Metrics/PerceivedComplexity
75
+
76
+ private
77
+
78
+ def empty_if_outcome?(node)
79
+ empty_branch_outcome?(node.if_branch)
80
+ end
81
+
82
+ def empty_else_outcome?(node)
83
+ empty_branch_outcome?(node.else_branch)
84
+ end
85
+
86
+ def empty_branch_outcome?(branch)
87
+ return false unless branch
88
+
89
+ branch.nil_type? || (branch.str_type? && branch.value.empty?)
90
+ end
91
+
92
+ def ternary_style_autocorrect(node, outcome, condition)
93
+ add_offense(node, message: MSG_TERNARY) do |corrector|
94
+ corrector.replace(node, "#{outcome} #{condition} #{node.condition.source}")
95
+ end
96
+ end
97
+ end
98
+ end
99
+ end
100
+ end
@@ -132,12 +132,10 @@ module RuboCop
132
132
  end
133
133
 
134
134
  def pattern_matching_nodes(condition)
135
- if condition.type?(:match_pattern, :match_pattern_p)
135
+ if condition.any_match_pattern_type?
136
136
  [condition]
137
137
  else
138
- condition.each_descendant.select do |node|
139
- node.type?(:match_pattern, :match_pattern_p)
140
- end
138
+ condition.each_descendant.select(&:any_match_pattern_type?)
141
139
  end
142
140
  end
143
141
 
@@ -5,15 +5,28 @@ module RuboCop
5
5
  module Style
6
6
  # Checks for blocks with one argument where `it` block parameter can be used.
7
7
  #
8
- # It provides three `EnforcedStyle` options:
8
+ # It provides four `EnforcedStyle` options:
9
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.
10
+ # 1. `allow_single_line` (default) ... Always uses the `it` block parameter in a single line.
11
+ # 2. `only_numbered_parameters` ... Detects only numbered block parameters.
12
+ # 3. `always` ... Always uses the `it` block parameter.
13
+ # 4. `disallow` ... Disallows the `it` block parameter.
13
14
  #
14
- # A single numbered parameter is detected when `only_numbered_parameters` or `always`.
15
+ # A single numbered parameter is detected when `allow_single_line`,
16
+ # `only_numbered_parameters`, or `always`.
15
17
  #
16
- # @example EnforcedStyle: only_numbered_parameters (default)
18
+ # @example EnforcedStyle: allow_single_line (default)
19
+ # # bad
20
+ # block do
21
+ # do_something(it)
22
+ # end
23
+ # block { do_something(_1) }
24
+ #
25
+ # # good
26
+ # block { do_something(it) }
27
+ # block { |named_param| do_something(named_param) }
28
+ #
29
+ # @example EnforcedStyle: only_numbered_parameters
17
30
  # # bad
18
31
  # block { do_something(_1) }
19
32
  #
@@ -42,8 +55,9 @@ module RuboCop
42
55
  extend TargetRubyVersion
43
56
  extend AutoCorrector
44
57
 
45
- MSG_USE_IT_BLOCK_PARAMETER = 'Use `it` block parameter.'
46
- MSG_AVOID_IT_BLOCK_PARAMETER = 'Avoid using `it` block parameter.'
58
+ MSG_USE_IT_PARAMETER = 'Use `it` block parameter.'
59
+ MSG_AVOID_IT_PARAMETER = 'Avoid using `it` block parameter.'
60
+ MSG_AVOID_IT_PARAMETER_MULTI_LINE = 'Avoid using numbered parameters for multi-line blocks.'
47
61
 
48
62
  minimum_target_ruby_version 3.4
49
63
 
@@ -57,7 +71,7 @@ module RuboCop
57
71
  variables = find_block_variables(node, node.first_argument.source)
58
72
 
59
73
  variables.each do |variable|
60
- add_offense(variable, message: MSG_USE_IT_BLOCK_PARAMETER) do |corrector|
74
+ add_offense(variable, message: MSG_USE_IT_PARAMETER) do |corrector|
61
75
  corrector.remove(node.arguments)
62
76
  corrector.replace(variable, 'it')
63
77
  end
@@ -71,19 +85,24 @@ module RuboCop
71
85
  variables = find_block_variables(node, '_1')
72
86
 
73
87
  variables.each do |variable|
74
- add_offense(variable, message: MSG_USE_IT_BLOCK_PARAMETER) do |corrector|
88
+ add_offense(variable, message: MSG_USE_IT_PARAMETER) do |corrector|
75
89
  corrector.replace(variable, 'it')
76
90
  end
77
91
  end
78
92
  end
79
93
 
80
94
  def on_itblock(node)
81
- return unless style == :disallow
95
+ case style
96
+ when :allow_single_line
97
+ return if node.single_line?
82
98
 
83
- variables = find_block_variables(node, 'it')
99
+ add_offense(node, message: MSG_AVOID_IT_PARAMETER_MULTI_LINE)
100
+ when :disallow
101
+ variables = find_block_variables(node, 'it')
84
102
 
85
- variables.each do |variable|
86
- add_offense(variable, message: MSG_AVOID_IT_BLOCK_PARAMETER)
103
+ variables.each do |variable|
104
+ add_offense(variable, message: MSG_AVOID_IT_PARAMETER)
105
+ end
87
106
  end
88
107
  end
89
108