rubocop 1.78.0 → 1.79.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 (41) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  3. data/config/default.yml +32 -19
  4. data/lib/rubocop/cop/internal_affairs/node_type_group.rb +3 -2
  5. data/lib/rubocop/cop/internal_affairs/useless_restrict_on_send.rb +1 -1
  6. data/lib/rubocop/cop/layout/empty_lines_after_module_inclusion.rb +99 -0
  7. data/lib/rubocop/cop/layout/space_around_keyword.rb +6 -1
  8. data/lib/rubocop/cop/layout/space_around_operators.rb +8 -0
  9. data/lib/rubocop/cop/lint/numeric_operation_with_constant_result.rb +1 -0
  10. data/lib/rubocop/cop/lint/redundant_safe_navigation.rb +101 -2
  11. data/lib/rubocop/cop/lint/require_range_parentheses.rb +1 -1
  12. data/lib/rubocop/cop/lint/rescue_type.rb +1 -1
  13. data/lib/rubocop/cop/lint/useless_numeric_operation.rb +1 -0
  14. data/lib/rubocop/cop/lint/utils/nil_receiver_checker.rb +121 -0
  15. data/lib/rubocop/cop/naming/method_name.rb +15 -1
  16. data/lib/rubocop/cop/naming/predicate_method.rb +4 -1
  17. data/lib/rubocop/cop/style/access_modifier_declarations.rb +1 -1
  18. data/lib/rubocop/cop/style/accessor_grouping.rb +13 -1
  19. data/lib/rubocop/cop/style/array_intersect.rb +51 -23
  20. data/lib/rubocop/cop/style/block_delimiters.rb +1 -1
  21. data/lib/rubocop/cop/style/conditional_assignment.rb +1 -1
  22. data/lib/rubocop/cop/style/dig_chain.rb +1 -1
  23. data/lib/rubocop/cop/style/exponential_notation.rb +1 -0
  24. data/lib/rubocop/cop/style/inverse_methods.rb +1 -1
  25. data/lib/rubocop/cop/style/it_assignment.rb +69 -12
  26. data/lib/rubocop/cop/style/it_block_parameter.rb +2 -0
  27. data/lib/rubocop/cop/style/method_call_with_args_parentheses/omit_parentheses.rb +2 -4
  28. data/lib/rubocop/cop/style/parallel_assignment.rb +3 -0
  29. data/lib/rubocop/cop/style/redundant_freeze.rb +1 -1
  30. data/lib/rubocop/cop/style/redundant_line_continuation.rb +1 -1
  31. data/lib/rubocop/cop/style/redundant_parentheses.rb +1 -0
  32. data/lib/rubocop/cop/style/single_line_methods.rb +3 -3
  33. data/lib/rubocop/cop/style/sole_nested_conditional.rb +30 -1
  34. data/lib/rubocop/cop/style/stabby_lambda_parentheses.rb +1 -1
  35. data/lib/rubocop/cop/variable_force.rb +16 -7
  36. data/lib/rubocop/cops_documentation_generator.rb +1 -0
  37. data/lib/rubocop/formatter/markdown_formatter.rb +1 -0
  38. data/lib/rubocop/formatter/pacman_formatter.rb +1 -0
  39. data/lib/rubocop/version.rb +1 -1
  40. data/lib/rubocop.rb +2 -0
  41. metadata +22 -6
@@ -0,0 +1,121 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Lint
6
+ module Utils
7
+ # Utility class that checks if the receiver can't be nil.
8
+ class NilReceiverChecker
9
+ NIL_METHODS = (nil.methods + %i[!]).to_set.freeze
10
+
11
+ def initialize(receiver, additional_nil_methods)
12
+ @receiver = receiver
13
+ @additional_nil_methods = additional_nil_methods
14
+ @checked_nodes = {}.compare_by_identity
15
+ end
16
+
17
+ def cant_be_nil?
18
+ sole_condition_of_parent_if?(@receiver) || _cant_be_nil?(@receiver.parent, @receiver)
19
+ end
20
+
21
+ private
22
+
23
+ # rubocop:disable Metrics
24
+ def _cant_be_nil?(node, receiver)
25
+ return false unless node
26
+
27
+ # For some nodes, we check their parent and then some children for these parents.
28
+ # This is added to avoid infinite loops.
29
+ return false if @checked_nodes.key?(node)
30
+
31
+ @checked_nodes[node] = true
32
+
33
+ case node.type
34
+ when :def, :class, :module, :sclass
35
+ return false
36
+ when :send
37
+ return non_nil_method?(node.method_name) if node.receiver == receiver
38
+
39
+ node.arguments.each do |argument|
40
+ return true if _cant_be_nil?(argument, receiver)
41
+ end
42
+
43
+ return true if _cant_be_nil?(node.receiver, receiver)
44
+ when :begin
45
+ return true if _cant_be_nil?(node.children.first, receiver)
46
+ when :if, :case
47
+ return true if _cant_be_nil?(node.condition, receiver)
48
+ when :and, :or
49
+ return true if _cant_be_nil?(node.lhs, receiver)
50
+ when :pair
51
+ if _cant_be_nil?(node.key, receiver) ||
52
+ _cant_be_nil?(node.value, receiver)
53
+ return true
54
+ end
55
+ when :when
56
+ node.each_condition do |condition|
57
+ return true if _cant_be_nil?(condition, receiver)
58
+ end
59
+ when :lvasgn, :ivasgn, :cvasgn, :gvasgn, :casgn
60
+ return true if _cant_be_nil?(node.expression, receiver)
61
+ end
62
+
63
+ # Due to how `if/else` are implemented (`elsif` is a child of `if` or another `elsif`),
64
+ # using left_siblings will not work correctly for them.
65
+ if !else_branch?(node) || (node.if_type? && !node.elsif?)
66
+ node.left_siblings.reverse_each do |sibling|
67
+ next unless sibling.is_a?(AST::Node)
68
+
69
+ return true if _cant_be_nil?(sibling, receiver)
70
+ end
71
+ end
72
+
73
+ if node.parent
74
+ _cant_be_nil?(node.parent, receiver)
75
+ else
76
+ false
77
+ end
78
+ end
79
+ # rubocop:enable Metrics
80
+
81
+ def non_nil_method?(method_name)
82
+ !NIL_METHODS.include?(method_name) && !@additional_nil_methods.include?(method_name)
83
+ end
84
+
85
+ # rubocop:disable Metrics/PerceivedComplexity
86
+ def sole_condition_of_parent_if?(node)
87
+ parent = node.parent
88
+
89
+ while parent
90
+ if parent.if_type?
91
+ if parent.condition == node
92
+ return true
93
+ elsif parent.elsif?
94
+ parent = find_top_if(parent)
95
+ end
96
+ elsif else_branch?(parent)
97
+ # Find the top `if` for `else`.
98
+ parent = parent.parent
99
+ end
100
+
101
+ parent = parent&.parent
102
+ end
103
+
104
+ false
105
+ end
106
+ # rubocop:enable Metrics/PerceivedComplexity
107
+
108
+ def else_branch?(node)
109
+ node.parent&.if_type? && node.parent.else_branch == node
110
+ end
111
+
112
+ def find_top_if(node)
113
+ node = node.parent while node.elsif?
114
+
115
+ node
116
+ end
117
+ end
118
+ end
119
+ end
120
+ end
121
+ end
@@ -94,6 +94,9 @@ module RuboCop
94
94
  MSG = 'Use %<style>s for method names.'
95
95
  MSG_FORBIDDEN = '`%<identifier>s` is forbidden, use another method name instead.'
96
96
 
97
+ OPERATOR_METHODS = %i[| ^ & <=> == === =~ > >= < <= << >> + - * /
98
+ % ** ~ +@ -@ !@ ~@ [] []= ! != !~ `].to_set.freeze
99
+
97
100
  # @!method sym_name(node)
98
101
  def_node_matcher :sym_name, '(sym $_name)'
99
102
 
@@ -103,11 +106,16 @@ module RuboCop
103
106
  # @!method new_struct?(node)
104
107
  def_node_matcher :new_struct?, '(send (const {nil? cbase} :Struct) :new ...)'
105
108
 
109
+ # @!method define_data?(node)
110
+ def_node_matcher :define_data?, '(send (const {nil? cbase} :Data) :define ...)'
111
+
106
112
  def on_send(node)
107
113
  if node.method?(:define_method) || node.method?(:define_singleton_method)
108
114
  handle_define_method(node)
109
115
  elsif new_struct?(node)
110
116
  handle_new_struct(node)
117
+ elsif define_data?(node)
118
+ handle_define_data(node)
111
119
  else
112
120
  handle_attr_accessor(node)
113
121
  end
@@ -139,6 +147,12 @@ module RuboCop
139
147
  end
140
148
  end
141
149
 
150
+ def handle_define_data(node)
151
+ node.arguments.select { |argument| argument.type?(:sym, :str) }.each do |name|
152
+ handle_method_name(name, name.value)
153
+ end
154
+ end
155
+
142
156
  def handle_attr_accessor(node)
143
157
  return unless (attrs = node.attribute_accessor?)
144
158
 
@@ -159,7 +173,7 @@ module RuboCop
159
173
 
160
174
  if forbidden_name?(name.to_s)
161
175
  register_forbidden_name(node)
162
- else
176
+ elsif !OPERATOR_METHODS.include?(name)
163
177
  check_name(node, name, range_position(node))
164
178
  end
165
179
  end
@@ -269,7 +269,10 @@ module RuboCop
269
269
  node.body ? [last_value(node.body)] : [s(:nil)]
270
270
  else
271
271
  # Branches with no value act as an implicit `nil`.
272
- node.branches.filter_map { |branch| branch ? last_value(branch) : s(:nil) }
272
+ branches = node.branches.map { |branch| branch ? last_value(branch) : s(:nil) }
273
+ # Missing else branches also act as an implicit `nil`.
274
+ branches.push(s(:nil)) unless node.else_branch
275
+ branches
273
276
  end
274
277
  end
275
278
 
@@ -348,7 +348,7 @@ module RuboCop
348
348
  end
349
349
 
350
350
  def remove_modifier_node_within_begin(corrector, modifier_node, begin_node)
351
- def_node = begin_node.children[1]
351
+ def_node = begin_node.children[begin_node.children.index(modifier_node) + 1]
352
352
  range = modifier_node.source_range.begin.join(def_node.source_range.begin)
353
353
  corrector.remove(range)
354
354
  end
@@ -84,7 +84,10 @@ module RuboCop
84
84
 
85
85
  def autocorrect(corrector, node)
86
86
  if (preferred_accessors = preferred_accessors(node))
87
- corrector.replace(node, preferred_accessors)
87
+ corrector.replace(
88
+ grouped_style? ? node : range_with_trailing_argument_comment(node),
89
+ preferred_accessors
90
+ )
88
91
  else
89
92
  range = range_with_surrounding_space(node.source_range, side: :left)
90
93
  corrector.remove(range)
@@ -196,6 +199,15 @@ module RuboCop
196
199
  end
197
200
  end.join("\n")
198
201
  end
202
+
203
+ def range_with_trailing_argument_comment(node)
204
+ comment = processed_source.ast_with_comments[node.last_argument].last
205
+ if comment
206
+ add_range(node.source_range, comment.source_range)
207
+ else
208
+ node
209
+ end
210
+ end
199
211
  end
200
212
  end
201
213
  end
@@ -5,12 +5,13 @@ module RuboCop
5
5
  module Style
6
6
  # In Ruby 3.1, `Array#intersect?` has been added.
7
7
  #
8
- # This cop identifies places where `(array1 & array2).any?`
9
- # or `(array1.intersection(array2)).any?` can be replaced by
10
- # `array1.intersect?(array2)`.
8
+ # This cop identifies places where:
9
+ # * `(array1 & array2).any?`
10
+ # * `(array1.intersection(array2)).any?`
11
+ # * `array1.any? { |elem| array2.member?(elem) }`
12
+ # can be replaced with `array1.intersect?(array2)`.
11
13
  #
12
- # The `array1.intersect?(array2)` method is faster than
13
- # `(array1 & array2).any?` and is more readable.
14
+ # `array1.intersect?(array2)` is faster and more readable.
14
15
  #
15
16
  # In cases like the following, compatibility is not ensured,
16
17
  # so it will not be detected when using block argument.
@@ -40,6 +41,10 @@ module RuboCop
40
41
  # array1.intersection(array2).empty?
41
42
  # array1.intersection(array2).none?
42
43
  #
44
+ # # bad
45
+ # array1.any? { |elem| array2.member?(elem) }
46
+ # array1.none? { |elem| array2.member?(elem) }
47
+ #
43
48
  # # good
44
49
  # array1.intersect?(array2)
45
50
  # !array1.intersect?(array2)
@@ -77,8 +82,26 @@ module RuboCop
77
82
  )
78
83
  PATTERN
79
84
 
80
- MSG = 'Use `%<negated>s%<receiver>s%<dot>sintersect?(%<argument>s)` ' \
81
- 'instead of `%<existing>s`.'
85
+ # @!method any_none_block_intersection(node)
86
+ def_node_matcher :any_none_block_intersection, <<~PATTERN
87
+ {
88
+ (block
89
+ (call $_receiver ${:any? :none?})
90
+ (args (arg _key))
91
+ (send $_argument :member? (lvar _key))
92
+ )
93
+ (numblock
94
+ (call $_receiver ${:any? :none?}) 1
95
+ (send $_argument :member? (lvar :_1))
96
+ )
97
+ (itblock
98
+ (call $_receiver ${:any? :none?}) :it
99
+ (send $_argument :member? (lvar :it))
100
+ )
101
+ }
102
+ PATTERN
103
+
104
+ MSG = 'Use `%<replacement>s` instead of `%<existing>s`.'
82
105
  STRAIGHT_METHODS = %i[present? any?].freeze
83
106
  NEGATED_METHODS = %i[blank? empty? none?].freeze
84
107
  RESTRICT_ON_SEND = (STRAIGHT_METHODS + NEGATED_METHODS).freeze
@@ -88,16 +111,25 @@ module RuboCop
88
111
  return unless (receiver, argument, method_name = bad_intersection?(node))
89
112
 
90
113
  dot = node.loc.dot.source
91
- message = message(receiver.source, argument.source, method_name, dot, node.source)
114
+ bang = straight?(method_name) ? '' : '!'
115
+ replacement = "#{bang}#{receiver.source}#{dot}intersect?(#{argument.source})"
92
116
 
93
- add_offense(node, message: message) do |corrector|
94
- bang = straight?(method_name) ? '' : '!'
95
-
96
- corrector.replace(node, "#{bang}#{receiver.source}#{dot}intersect?(#{argument.source})")
97
- end
117
+ register_offense(node, replacement)
98
118
  end
99
119
  alias on_csend on_send
100
120
 
121
+ def on_block(node)
122
+ return unless (receiver, method_name, argument = any_none_block_intersection(node))
123
+
124
+ dot = node.send_node.loc.dot.source
125
+ bang = method_name == :any? ? '' : '!'
126
+ replacement = "#{bang}#{receiver.source}#{dot}intersect?(#{argument.source})"
127
+
128
+ register_offense(node, replacement)
129
+ end
130
+ alias on_numblock on_block
131
+ alias on_itblock on_block
132
+
101
133
  private
102
134
 
103
135
  def bad_intersection?(node)
@@ -114,16 +146,12 @@ module RuboCop
114
146
  STRAIGHT_METHODS.include?(method_name.to_sym)
115
147
  end
116
148
 
117
- def message(receiver, argument, method_name, dot, existing)
118
- negated = straight?(method_name) ? '' : '!'
119
- format(
120
- MSG,
121
- negated: negated,
122
- receiver: receiver,
123
- argument: argument,
124
- dot: dot,
125
- existing: existing
126
- )
149
+ def register_offense(node, replacement)
150
+ message = format(MSG, replacement: replacement, existing: node.source)
151
+
152
+ add_offense(node, message: message) do |corrector|
153
+ corrector.replace(node, replacement)
154
+ end
127
155
  end
128
156
  end
129
157
  end
@@ -4,7 +4,7 @@
4
4
  module RuboCop
5
5
  module Cop
6
6
  module Style
7
- # Check for uses of braces or do/end around single line or
7
+ # Checks for uses of braces or do/end around single line or
8
8
  # multi-line blocks.
9
9
  #
10
10
  # Methods that can be either procedural or functional and cannot be
@@ -111,7 +111,7 @@ module RuboCop
111
111
  end
112
112
  end
113
113
 
114
- # Check for `if` and `case` statements where each branch is used for
114
+ # Checks for `if` and `case` statements where each branch is used for
115
115
  # both the assignment and comparison of the same variable
116
116
  # when using the return of the condition can be used instead.
117
117
  #
@@ -3,7 +3,7 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Style
6
- # Check for chained `dig` calls that can be collapsed into a single `dig`.
6
+ # Checks for chained `dig` calls that can be collapsed into a single `dig`.
7
7
  #
8
8
  # @safety
9
9
  # This cop is unsafe because it cannot be guaranteed that the receiver
@@ -59,6 +59,7 @@ module RuboCop
59
59
  #
60
60
  class ExponentialNotation < Base
61
61
  include ConfigurableEnforcedStyle
62
+
62
63
  MESSAGES = {
63
64
  scientific: 'Use a mantissa >= 1 and < 10.',
64
65
  engineering: 'Use an exponent divisible by 3 and a mantissa >= 0.1 and < 1000.',
@@ -3,7 +3,7 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Style
6
- # Check for usages of not (`not` or `!`) called on a method
6
+ # Checks for usages of not (`not` or `!`) called on a method
7
7
  # when an inverse of that method can be used instead.
8
8
  #
9
9
  # Methods that can be inverted by a not (`not` or `!`) should be defined
@@ -3,33 +3,90 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Style
6
- # Checks for assignments to a local `it` variable inside a block
6
+ # Checks for local variables and method parameters named `it`,
7
7
  # where `it` can refer to the first anonymous parameter as of Ruby 3.4.
8
+ # Use a meaningful variable name instead.
8
9
  #
9
- # Although Ruby allows reassigning `it` in these cases, it could
10
+ # NOTE: Although Ruby allows reassigning `it` in these cases, it could
10
11
  # cause confusion if `it` is used as a block parameter elsewhere.
11
- # For consistency, this also applies to numblocks and blocks with
12
- # parameters, even though `it` cannot be used in those cases.
13
12
  #
14
13
  # @example
15
14
  # # bad
16
- # foo { it = 5 }
17
- # foo { |bar| it = bar }
18
- # foo { it = _2 }
15
+ # it = 5
19
16
  #
20
- # # good - use a different variable name
21
- # foo { var = 5 }
22
- # foo { |bar| var = bar }
23
- # foo { bar = _2 }
17
+ # # good
18
+ # var = 5
19
+ #
20
+ # # bad
21
+ # def foo(it)
22
+ # end
23
+ #
24
+ # # good
25
+ # def foo(arg)
26
+ # end
27
+ #
28
+ # # bad
29
+ # def foo(it = 5)
30
+ # end
31
+ #
32
+ # # good
33
+ # def foo(arg = 5)
34
+ # end
35
+ #
36
+ # # bad
37
+ # def foo(*it)
38
+ # end
39
+ #
40
+ # # good
41
+ # def foo(*args)
42
+ # end
43
+ #
44
+ # # bad
45
+ # def foo(it:)
46
+ # end
47
+ #
48
+ # # good
49
+ # def foo(arg:)
50
+ # end
51
+ #
52
+ # # bad
53
+ # def foo(it: 5)
54
+ # end
55
+ #
56
+ # # good
57
+ # def foo(arg: 5)
58
+ # end
59
+ #
60
+ # # bad
61
+ # def foo(**it)
62
+ # end
63
+ #
64
+ # # good
65
+ # def foo(**kwargs)
66
+ # end
67
+ #
68
+ # # bad
69
+ # def foo(&it)
70
+ # end
71
+ #
72
+ # # good
73
+ # def foo(&block)
74
+ # end
24
75
  class ItAssignment < Base
25
76
  MSG = '`it` is the default block parameter; consider another name.'
26
77
 
27
78
  def on_lvasgn(node)
28
79
  return unless node.name == :it
29
- return unless node.each_ancestor(:any_block).any?
30
80
 
31
81
  add_offense(node.loc.name)
32
82
  end
83
+ alias on_arg on_lvasgn
84
+ alias on_optarg on_lvasgn
85
+ alias on_restarg on_lvasgn
86
+ alias on_blockarg on_lvasgn
87
+ alias on_kwarg on_lvasgn
88
+ alias on_kwoptarg on_lvasgn
89
+ alias on_kwrestarg on_lvasgn
33
90
  end
34
91
  end
35
92
  end
@@ -109,6 +109,8 @@ module RuboCop
109
109
  private
110
110
 
111
111
  def find_block_variables(node, block_argument_name)
112
+ return [] unless node.body
113
+
112
114
  node.body.each_descendant(:lvar).select do |descendant|
113
115
  descendant.source == block_argument_name
114
116
  end
@@ -222,11 +222,9 @@ module RuboCop
222
222
  end
223
223
 
224
224
  def unary_literal?(node)
225
- # NOTE: should be removed after releasing https://github.com/rubocop/rubocop-ast/pull/379
226
- return node.source.match?(/\A[+-]/) if node.complex_type?
225
+ return true if node.numeric_type? && node.sign?
227
226
 
228
- (node.numeric_type? && node.sign?) ||
229
- (node.parent&.send_type? && node.parent.unary_operation?)
227
+ node.parent&.send_type? && node.parent.unary_operation?
230
228
  end
231
229
 
232
230
  def assigned_before?(node, target)
@@ -29,6 +29,8 @@ module RuboCop
29
29
  MSG = 'Do not use parallel assignment.'
30
30
 
31
31
  def on_masgn(node) # rubocop:disable Metrics/AbcSize
32
+ return if part_of_ignored_node?(node)
33
+
32
34
  rhs = node.rhs
33
35
  rhs = rhs.body if rhs.rescue_type?
34
36
  rhs_elements = Array(rhs).compact # edge case for one constant
@@ -41,6 +43,7 @@ module RuboCop
41
43
  add_offense(range) do |corrector|
42
44
  autocorrect(corrector, node, rhs)
43
45
  end
46
+ ignore_node(node)
44
47
  end
45
48
 
46
49
  private
@@ -3,7 +3,7 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Style
6
- # Check for uses of `Object#freeze` on immutable objects.
6
+ # Checks for uses of `Object#freeze` on immutable objects.
7
7
  #
8
8
  # NOTE: `Regexp` and `Range` literals are frozen objects since Ruby 3.0.
9
9
  #
@@ -3,7 +3,7 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Style
6
- # Check for redundant line continuation.
6
+ # Checks for redundant line continuation.
7
7
  #
8
8
  # This cop marks a line continuation as redundant if removing the backslash
9
9
  # does not result in a syntax error.
@@ -169,6 +169,7 @@ module RuboCop
169
169
  end
170
170
  return 'an interpolated expression' if interpolation?(begin_node)
171
171
  return 'a method argument' if argument_of_parenthesized_method_call?(begin_node, node)
172
+ return 'a one-line rescue' if !begin_node.parent&.call_type? && node.rescue_type?
172
173
 
173
174
  return if begin_node.chained?
174
175
 
@@ -65,7 +65,7 @@ module RuboCop
65
65
  return false if target_ruby_version < 3.0
66
66
  return false if disallow_endless_method_style?
67
67
  return false unless body_node
68
- return false if body_node.parent.assignment_method? ||
68
+ return false if body_node.basic_conditional? || body_node.parent.assignment_method? ||
69
69
  NOT_SUPPORTED_ENDLESS_METHOD_BODY_TYPES.include?(body_node.type)
70
70
 
71
71
  !body_node.type?(:begin, :kwbegin)
@@ -86,10 +86,10 @@ module RuboCop
86
86
  end
87
87
 
88
88
  def correct_to_endless(corrector, node)
89
- self_receiver = node.self_receiver? ? 'self.' : ''
89
+ receiver = "#{node.receiver.source}." if node.receiver
90
90
  arguments = node.arguments.any? ? node.arguments.source : '()'
91
91
  body_source = method_body_source(node.body)
92
- replacement = "def #{self_receiver}#{node.method_name}#{arguments} = #{body_source}"
92
+ replacement = "def #{receiver}#{node.method_name}#{arguments} = #{body_source}"
93
93
 
94
94
  corrector.replace(node, replacement)
95
95
  end
@@ -175,6 +175,8 @@ module RuboCop
175
175
 
176
176
  if parenthesize_method?(condition)
177
177
  parenthesized_method_arguments(condition)
178
+ elsif condition.and_type?
179
+ parenthesized_and(condition)
178
180
  else
179
181
  "(#{condition.source})"
180
182
  end
@@ -186,12 +188,19 @@ module RuboCop
186
188
  end
187
189
 
188
190
  def add_parentheses?(node)
189
- return true if node.assignment? || (node.operator_keyword? && !node.and_type?)
191
+ return true if node.assignment? || node.or_type?
192
+ return true if assignment_in_and?(node)
190
193
  return false unless node.call_type?
191
194
 
192
195
  (node.arguments.any? && !node.parenthesized?) || node.prefix_not?
193
196
  end
194
197
 
198
+ def assignment_in_and?(node)
199
+ return false unless node.and_type?
200
+
201
+ node.each_descendant.any?(&:assignment?)
202
+ end
203
+
195
204
  def parenthesized_method_arguments(node)
196
205
  method_call = node.source_range.begin.join(node.loc.selector.end).source
197
206
  arguments = node.first_argument.source_range.begin.join(node.source_range.end).source
@@ -199,6 +208,26 @@ module RuboCop
199
208
  "#{method_call}(#{arguments})"
200
209
  end
201
210
 
211
+ def parenthesized_and(node)
212
+ # We only need to add parentheses around the last clause if it's an assignment,
213
+ # because other clauses will be unchanged by merging conditionals.
214
+ lhs = node.lhs.source
215
+ rhs = parenthesized_and_clause(node.rhs)
216
+ operator = range_with_surrounding_space(node.loc.operator, whitespace: true).source
217
+
218
+ "#{lhs}#{operator}#{rhs}"
219
+ end
220
+
221
+ def parenthesized_and_clause(node)
222
+ if node.and_type?
223
+ parenthesized_and(node)
224
+ elsif node.assignment?
225
+ "(#{node.source})"
226
+ else
227
+ node.source
228
+ end
229
+ end
230
+
202
231
  def allow_modifier?
203
232
  cop_config['AllowModifier']
204
233
  end
@@ -3,7 +3,7 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Style
6
- # Check for parentheses around stabby lambda arguments.
6
+ # Checks for parentheses around stabby lambda arguments.
7
7
  # There are two different styles. Defaults to `require_parentheses`.
8
8
  #
9
9
  # @example EnforcedStyle: require_parentheses (default)