rubocop 1.92.0 → 1.92.2

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 (104) hide show
  1. checksums.yaml +4 -4
  2. data/config/default.yml +1 -0
  3. data/lib/rubocop/config.rb +0 -4
  4. data/lib/rubocop/config_obsoletion.rb +9 -0
  5. data/lib/rubocop/cop/badge.rb +1 -1
  6. data/lib/rubocop/cop/correctors/each_to_for_corrector.rb +16 -1
  7. data/lib/rubocop/cop/correctors/empty_line_corrector.rb +1 -1
  8. data/lib/rubocop/cop/correctors/lambda_literal_to_method_corrector.rb +2 -2
  9. data/lib/rubocop/cop/correctors/multiline_literal_brace_corrector.rb +34 -0
  10. data/lib/rubocop/cop/layout/argument_alignment.rb +5 -1
  11. data/lib/rubocop/cop/layout/class_structure.rb +21 -7
  12. data/lib/rubocop/cop/layout/dot_position.rb +25 -15
  13. data/lib/rubocop/cop/layout/empty_line_after_multiline_condition.rb +11 -7
  14. data/lib/rubocop/cop/layout/empty_lines_after_module_inclusion.rb +1 -1
  15. data/lib/rubocop/cop/layout/empty_lines_around_block_body.rb +9 -3
  16. data/lib/rubocop/cop/layout/end_alignment.rb +5 -15
  17. data/lib/rubocop/cop/layout/extra_spacing.rb +18 -7
  18. data/lib/rubocop/cop/layout/first_argument_indentation.rb +26 -1
  19. data/lib/rubocop/cop/layout/first_array_element_line_break.rb +15 -0
  20. data/lib/rubocop/cop/layout/first_hash_element_indentation.rb +11 -5
  21. data/lib/rubocop/cop/layout/heredoc_argument_closing_parenthesis.rb +5 -3
  22. data/lib/rubocop/cop/layout/indentation_consistency.rb +22 -5
  23. data/lib/rubocop/cop/layout/line_length.rb +127 -11
  24. data/lib/rubocop/cop/layout/multiline_hash_brace_layout.rb +6 -4
  25. data/lib/rubocop/cop/layout/multiline_method_call_indentation.rb +16 -5
  26. data/lib/rubocop/cop/layout/rescue_ensure_alignment.rb +1 -1
  27. data/lib/rubocop/cop/layout/single_line_block_chain.rb +3 -3
  28. data/lib/rubocop/cop/layout/space_around_operators.rb +31 -4
  29. data/lib/rubocop/cop/layout/space_before_block_braces.rb +12 -0
  30. data/lib/rubocop/cop/layout/space_inside_block_braces.rb +1 -1
  31. data/lib/rubocop/cop/layout/space_inside_range_literal.rb +29 -11
  32. data/lib/rubocop/cop/layout/space_inside_reference_brackets.rb +2 -12
  33. data/lib/rubocop/cop/lint/ambiguous_regexp_literal.rb +6 -1
  34. data/lib/rubocop/cop/lint/heredoc_method_call_position.rb +4 -0
  35. data/lib/rubocop/cop/lint/interpolation_check.rb +20 -17
  36. data/lib/rubocop/cop/lint/nested_percent_literal.rb +3 -0
  37. data/lib/rubocop/cop/lint/number_conversion.rb +2 -0
  38. data/lib/rubocop/cop/lint/parentheses_as_grouped_expression.rb +22 -1
  39. data/lib/rubocop/cop/lint/percent_string_array.rb +75 -9
  40. data/lib/rubocop/cop/lint/redundant_cop_disable_directive.rb +27 -8
  41. data/lib/rubocop/cop/lint/redundant_splat_expansion.rb +11 -4
  42. data/lib/rubocop/cop/lint/safe_navigation_consistency.rb +47 -1
  43. data/lib/rubocop/cop/lint/symbol_conversion.rb +1 -0
  44. data/lib/rubocop/cop/lint/useless_setter_call.rb +7 -4
  45. data/lib/rubocop/cop/mixin/check_line_breakable.rb +7 -6
  46. data/lib/rubocop/cop/mixin/check_single_line_suitability.rb +3 -1
  47. data/lib/rubocop/cop/mixin/comments_help.rb +1 -1
  48. data/lib/rubocop/cop/mixin/endless_method_body_help.rb +36 -0
  49. data/lib/rubocop/cop/mixin/hash_alignment_styles.rb +26 -5
  50. data/lib/rubocop/cop/mixin/statement_modifier.rb +17 -3
  51. data/lib/rubocop/cop/mixin/trailing_comma.rb +1 -1
  52. data/lib/rubocop/cop/mixin/visibility_help.rb +2 -1
  53. data/lib/rubocop/cop/naming/rescued_exceptions_variable_name.rb +1 -1
  54. data/lib/rubocop/cop/style/access_modifier_declarations.rb +30 -2
  55. data/lib/rubocop/cop/style/and_or.rb +34 -11
  56. data/lib/rubocop/cop/style/arguments_forwarding.rb +28 -2
  57. data/lib/rubocop/cop/style/block_delimiters.rb +6 -0
  58. data/lib/rubocop/cop/style/case_equality.rb +14 -3
  59. data/lib/rubocop/cop/style/class_and_module_children.rb +6 -3
  60. data/lib/rubocop/cop/style/class_methods_definitions.rb +7 -0
  61. data/lib/rubocop/cop/style/collection_querying.rb +79 -46
  62. data/lib/rubocop/cop/style/combinable_loops.rb +4 -0
  63. data/lib/rubocop/cop/style/concat_array_literals.rb +13 -1
  64. data/lib/rubocop/cop/style/empty_string_inside_interpolation.rb +53 -11
  65. data/lib/rubocop/cop/style/endless_method.rb +38 -6
  66. data/lib/rubocop/cop/style/float_division.rb +16 -1
  67. data/lib/rubocop/cop/style/for.rb +14 -1
  68. data/lib/rubocop/cop/style/guard_clause.rb +22 -21
  69. data/lib/rubocop/cop/style/hash_as_last_array_item.rb +4 -0
  70. data/lib/rubocop/cop/style/hash_lookup_method.rb +10 -0
  71. data/lib/rubocop/cop/style/if_unless_modifier.rb +19 -12
  72. data/lib/rubocop/cop/style/it_block_parameter.rb +63 -2
  73. data/lib/rubocop/cop/style/lambda.rb +16 -1
  74. data/lib/rubocop/cop/style/method_call_with_args_parentheses/omit_parentheses.rb +36 -4
  75. data/lib/rubocop/cop/style/method_call_with_args_parentheses.rb +1 -0
  76. data/lib/rubocop/cop/style/method_def_parentheses.rb +9 -1
  77. data/lib/rubocop/cop/style/mixin_grouping.rb +12 -3
  78. data/lib/rubocop/cop/style/mutable_constant.rb +25 -7
  79. data/lib/rubocop/cop/style/numeric_predicate.rb +7 -0
  80. data/lib/rubocop/cop/style/parentheses_around_condition.rb +19 -1
  81. data/lib/rubocop/cop/style/quoted_symbols.rb +5 -7
  82. data/lib/rubocop/cop/style/raise_args.rb +80 -11
  83. data/lib/rubocop/cop/style/reduce_to_hash.rb +32 -34
  84. data/lib/rubocop/cop/style/redundant_begin.rb +26 -3
  85. data/lib/rubocop/cop/style/redundant_exception.rb +13 -9
  86. data/lib/rubocop/cop/style/redundant_freeze.rb +1 -2
  87. data/lib/rubocop/cop/style/redundant_interpolation_unfreeze.rb +24 -1
  88. data/lib/rubocop/cop/style/redundant_self.rb +9 -1
  89. data/lib/rubocop/cop/style/redundant_self_assignment_branch.rb +28 -0
  90. data/lib/rubocop/cop/style/redundant_sort.rb +38 -4
  91. data/lib/rubocop/cop/style/require_order.rb +16 -8
  92. data/lib/rubocop/cop/style/rescue_modifier.rb +6 -0
  93. data/lib/rubocop/cop/style/send_with_literal_method_name.rb +4 -0
  94. data/lib/rubocop/cop/style/single_line_methods.rb +26 -0
  95. data/lib/rubocop/cop/style/string_concatenation.rb +12 -1
  96. data/lib/rubocop/cop/style/struct_inheritance.rb +6 -0
  97. data/lib/rubocop/cop/style/symbol_proc.rb +1 -1
  98. data/lib/rubocop/lsp/routes.rb +23 -23
  99. data/lib/rubocop/plugin/configuration_integrator.rb +24 -3
  100. data/lib/rubocop/runner.rb +1 -1
  101. data/lib/rubocop/server/cache.rb +4 -4
  102. data/lib/rubocop/target_ruby.rb +21 -9
  103. data/lib/rubocop/version.rb +1 -1
  104. metadata +2 -2
@@ -9,6 +9,11 @@ module RuboCop
9
9
  # example, mistranslating an array of literals to percent string notation)
10
10
  # rather than meant to be part of the resulting strings.
11
11
  #
12
+ # When a quoted string contains whitespace, `%w` splits it into separate values,
13
+ # so removing the quotes would not restore the intended string.
14
+ # Such an array is corrected to an array literal instead, as long as its content
15
+ # reads as one with only string elements.
16
+ #
12
17
  # @safety
13
18
  # The cop is unsafe because the correction changes the values in the array
14
19
  # and that might have been done purposely.
@@ -26,6 +31,12 @@ module RuboCop
26
31
  #
27
32
  # # good
28
33
  # %w(foo bar)
34
+ #
35
+ # # bad - the quoted strings are split at whitespace
36
+ # %w('foo bar', 'baz')
37
+ #
38
+ # # good
39
+ # ['foo bar', 'baz']
29
40
  class PercentStringArray < Base
30
41
  include PercentLiteral
31
42
  extend AutoCorrector
@@ -36,27 +47,69 @@ module RuboCop
36
47
 
37
48
  MSG = "Within `%w`/`%W`, quotes and ',' are unnecessary and may be " \
38
49
  'unwanted in the resulting strings.'
50
+ MSG_SPLIT_STRING = 'Within `%w`/`%W`, quoted strings are split at whitespace; ' \
51
+ 'use an array literal instead.'
39
52
 
40
53
  def on_array(node)
41
54
  process(node, '%w', '%W')
42
55
  end
43
56
 
44
57
  def on_percent_literal(node)
45
- return unless contains_quotes_or_commas?(node)
58
+ if split_quoted_string?(node)
59
+ register_split_quoted_string(node)
60
+ elsif contains_quotes_or_commas?(node)
61
+ register_quotes_or_commas(node)
62
+ end
63
+ end
46
64
 
47
- add_offense(node) do |corrector|
48
- node.each_value do |value|
49
- range = value.source_range
65
+ private
50
66
 
51
- match = range.source.match(TRAILING_QUOTE)
52
- corrector.remove_trailing(range, match[0].length) if match
67
+ def split_quoted_string?(node)
68
+ sources = node.values.map { |value| value.source.delete_suffix(',') }
53
69
 
54
- corrector.remove_leading(range, 1) if LEADING_QUOTE.match?(range.source)
55
- end
70
+ sources.each_with_index.any? do |source, index|
71
+ next false unless (quote = unclosed_quote(source))
72
+
73
+ sources.drop(index + 1).any? { |rest| closing_quote?(rest, quote) }
56
74
  end
57
75
  end
58
76
 
59
- private
77
+ def unclosed_quote(source)
78
+ quote = source[0]
79
+ return unless LEADING_QUOTE.match?(quote)
80
+
81
+ quote if source.length > 1 && !source.end_with?(quote)
82
+ end
83
+
84
+ def closing_quote?(source, quote)
85
+ source.end_with?(quote) && (source.length == 1 || !source.start_with?(quote))
86
+ end
87
+
88
+ def register_split_quoted_string(node)
89
+ add_offense(node, message: MSG_SPLIT_STRING) do |corrector|
90
+ next unless (array_literal = array_literal_source(node))
91
+
92
+ corrector.replace(node, array_literal)
93
+ end
94
+ end
95
+
96
+ def array_literal_source(node)
97
+ content = range_between(node.loc.begin.end_pos, node.loc.end.begin_pos).source
98
+ array_literal = "[#{content}]"
99
+
100
+ array_literal if string_array?(parse(array_literal))
101
+ end
102
+
103
+ def string_array?(processed_source)
104
+ return false unless processed_source.valid_syntax? && processed_source.comments.empty?
105
+ return false unless (array = processed_source.ast).array_type?
106
+
107
+ array.children.all? { |child| quoted_string?(child) }
108
+ end
109
+
110
+ def quoted_string?(node)
111
+ node.type?(:str, :dstr) && node.loc?(:begin)
112
+ end
60
113
 
61
114
  def contains_quotes_or_commas?(node)
62
115
  node.values.any? do |value|
@@ -68,6 +121,19 @@ module RuboCop
68
121
  QUOTES_AND_COMMAS.any? { |pat| literal.match?(pat) }
69
122
  end
70
123
  end
124
+
125
+ def register_quotes_or_commas(node)
126
+ add_offense(node) do |corrector|
127
+ node.each_value do |value|
128
+ range = value.source_range
129
+
130
+ match = range.source.match(TRAILING_QUOTE)
131
+ corrector.remove_trailing(range, match[0].length) if match
132
+
133
+ corrector.remove_leading(range, 1) if LEADING_QUOTE.match?(range.source)
134
+ end
135
+ end
136
+ end
71
137
  end
72
138
  end
73
139
  end
@@ -146,8 +146,8 @@ module RuboCop
146
146
  comment = directive_comment(line_range)
147
147
  next if skip_directive?(comment)
148
148
 
149
- next_range = line_ranges[line_range_index + 1]
150
- redundant = find_redundant_directive(cop, comment, line_range, next_range)
149
+ following_ranges = line_ranges[(line_range_index + 1)..]
150
+ redundant = find_redundant_directive(cop, comment, line_range, following_ranges)
151
151
  yield comment, redundant if redundant
152
152
  end
153
153
  end
@@ -191,14 +191,33 @@ module RuboCop
191
191
  directive.push? || directive.pop? || directive.next?
192
192
  end
193
193
 
194
- def find_redundant_directive(cop, comment, line_range, next_range)
195
- if all_disabled?(comment)
196
- find_redundant_all(line_range, next_range)
197
- elsif department_disabled?(cop, comment)
198
- find_redundant_department(cop, line_range)
194
+ def find_redundant_directive(cop, comment, line_range, following_ranges)
195
+ return find_redundant_all(line_range, following_ranges.first) if all_disabled?(comment)
196
+
197
+ range = line_range_through_repeated_disables(cop, line_range, following_ranges)
198
+ if department_disabled?(cop, comment)
199
+ find_redundant_department(cop, range)
199
200
  else
200
- find_redundant_cop(cop, line_range)
201
+ find_redundant_cop(cop, range)
202
+ end
203
+ end
204
+
205
+ def line_range_through_repeated_disables(cop, line_range, following_ranges)
206
+ last_range = line_range
207
+ following_ranges.each do |range|
208
+ break unless followed_ranges?(last_range, range) && repeated_disable?(cop, range)
209
+
210
+ last_range = range
201
211
  end
212
+
213
+ line_range.begin..last_range.end
214
+ end
215
+
216
+ def repeated_disable?(cop, range)
217
+ comment = directive_comment(range)
218
+ return false if comment.nil? || skip_directive?(comment)
219
+
220
+ !all_disabled?(comment) && !department_disabled?(cop, comment)
202
221
  end
203
222
 
204
223
  # rubocop:disable-next Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
@@ -118,10 +118,7 @@ module RuboCop
118
118
  def redundant_splat_expansion(node)
119
119
  literal_expansion(node) do |expanded_item|
120
120
  if expanded_item.send_type?
121
- return if array_new_inside_array_literal?(expanded_item)
122
-
123
- grandparent = node.parent.parent
124
- return if grandparent && !ASSIGNMENT_TYPES.include?(grandparent.type)
121
+ return unless redundant_array_new_splat?(node, expanded_item)
125
122
  # An empty array/percent literal (`*[]`, `*%w()`, ...) expands to nothing, so
126
123
  # removing the splat would produce invalid or semantically different code.
127
124
  elsif expanded_item.array_type? && expanded_item.children.empty?
@@ -132,6 +129,16 @@ module RuboCop
132
129
  end
133
130
  end
134
131
 
132
+ # A splatted `Array.new` passed as a method argument provides the arguments,
133
+ # so the splat is only redundant directly inside an array.
134
+ def redundant_array_new_splat?(node, array_new_node)
135
+ return false unless node.parent.array_type?
136
+ return false if array_new_inside_array_literal?(array_new_node)
137
+
138
+ grandparent = node.parent.parent
139
+ !grandparent || ASSIGNMENT_TYPES.include?(grandparent.type)
140
+ end
141
+
135
142
  def array_new_inside_array_literal?(array_new_node)
136
143
  return false unless array_new?(array_new_node)
137
144
 
@@ -60,7 +60,7 @@ module RuboCop
60
60
 
61
61
  rest_operands = grouped_operands[begin_of_rest_operands..]
62
62
  rest_operands.each do |operand|
63
- next if already_appropriate_call?(operand, dot_op)
63
+ next unless correction_needed?(operand, dot_op, grouped_operands)
64
64
 
65
65
  register_offense(operand, dot_op)
66
66
  end
@@ -100,12 +100,58 @@ module RuboCop
100
100
  end
101
101
  end
102
102
 
103
+ def correction_needed?(operand, dot_op, grouped_operands)
104
+ return false if already_appropriate_call?(operand, dot_op)
105
+
106
+ dot_op == '&.' || receiver_guaranteed?(operand, grouped_operands)
107
+ end
108
+
103
109
  def already_appropriate_call?(operand, dot_op)
104
110
  return true if operand.safe_navigation? && dot_op == '&.'
105
111
 
106
112
  (operand.dot? || operand.operator_method?) && dot_op == '.'
107
113
  end
108
114
 
115
+ def receiver_guaranteed?(operand, grouped_operands)
116
+ grouped_operands.take_while { |guard| !guard.equal?(operand) }.any? do |guard|
117
+ guaranteed_by?(guard, operand)
118
+ end
119
+ end
120
+
121
+ def guaranteed_by?(guard, operand)
122
+ return false unless (path = logical_path(guard, operand))
123
+
124
+ truthiness = path.last.first.and_type? ? :truthy : :falsey
125
+ path[0...-1].reverse_each do |node, child|
126
+ return false unless (truthiness = child_truthiness(node, child, truthiness))
127
+ end
128
+
129
+ !guard.csend_type? || truthiness == :truthy
130
+ end
131
+
132
+ def logical_path(guard, operand)
133
+ path = []
134
+ child = guard
135
+
136
+ guard.each_ancestor do |node|
137
+ unless node.begin_type?
138
+ return nil unless node.operator_keyword?
139
+
140
+ path << [node, child]
141
+ end
142
+ return path if operand.each_ancestor.any? { |ancestor| ancestor.equal?(node) }
143
+
144
+ child = node
145
+ end
146
+ end
147
+
148
+ def child_truthiness(node, child, truthiness)
149
+ deciding = node.and_type? ? :truthy : :falsey
150
+ return truthiness == deciding ? deciding : :unknown if node.lhs.equal?(child)
151
+
152
+ deciding if truthiness == deciding
153
+ end
154
+
109
155
  def register_offense(operand, dot_operator)
110
156
  offense_range = operand.operator_method? ? operand : operand.loc.dot
111
157
  message = dot_operator == '.' ? USE_DOT_MSG : USE_SAFE_NAVIGATION_MSG
@@ -185,6 +185,7 @@ module RuboCop
185
185
  ignore_node(key)
186
186
 
187
187
  next if requires_quotes?(key)
188
+ next if key.parent.value_omission?
188
189
  next if properly_quoted?(key.source, %("#{key.value}"))
189
190
 
190
191
  correction = %("#{key.value}")
@@ -35,15 +35,13 @@ module RuboCop
35
35
  ASSIGNMENT_TYPES = %i[lvasgn ivasgn cvasgn gvasgn].freeze
36
36
 
37
37
  def on_def(node)
38
- return unless node.body
38
+ return if !node.body || node.void_context?
39
39
 
40
40
  last_expr = last_expression(node.body)
41
41
  return unless setter_call_to_local_variable?(last_expr)
42
42
 
43
- tracker = MethodVariableTracker.new(node.body)
44
43
  receiver, = *last_expr
45
- variable_name, = *receiver
46
- return unless tracker.contain_local_object?(variable_name)
44
+ return unless contain_local_object?(node.body, receiver)
47
45
 
48
46
  loc_name = receiver.loc.name
49
47
 
@@ -66,6 +64,11 @@ module RuboCop
66
64
  expression.is_a?(Array) ? expression.last : expression
67
65
  end
68
66
 
67
+ def contain_local_object?(body, receiver)
68
+ variable_name, = *receiver
69
+ MethodVariableTracker.new(body).contain_local_object?(variable_name)
70
+ end
71
+
69
72
  # This class tracks variable assignments in a method body
70
73
  # and if a variable contains object passed as argument at the end of
71
74
  # the method.
@@ -76,11 +76,7 @@ module RuboCop
76
76
  def extract_first_element_over_column_limit(node, elements, max)
77
77
  line = node.first_line
78
78
 
79
- # If a `send` or `csend` node is not parenthesized, don't move the first element, because it
80
- # can result in changed behavior or a syntax error.
81
- if node.call_type? && !node.parenthesized? && !first_argument_is_heredoc?(node)
82
- elements = elements.drop(1)
83
- end
79
+ elements = elements.drop(1) unless first_element_movable?(node)
84
80
 
85
81
  i = 0
86
82
  i += 1 while within_column_limit?(elements[i], max, line)
@@ -93,7 +89,12 @@ module RuboCop
93
89
  end
94
90
 
95
91
  # @api private
96
- def first_argument_is_heredoc?(node)
92
+ # If a method call or definition is not parenthesized, the first element cannot be moved,
93
+ # because it can result in changed behavior or a syntax error.
94
+ def first_element_movable?(node)
95
+ return !node.arguments.loc.begin.nil? if node.any_def_type?
96
+ return true if !node.call_type? || node.parenthesized?
97
+
97
98
  node.first_argument.respond_to?(:heredoc?) && node.first_argument.heredoc?
98
99
  end
99
100
 
@@ -5,6 +5,8 @@ module RuboCop
5
5
  # Checks for code on multiple lines that could be rewritten on a single line
6
6
  # without changing semantics or exceeding the `Max` parameter of `Layout/LineLength`.
7
7
  module CheckSingleLineSuitability
8
+ include LineLengthHelp
9
+
8
10
  def suitable_as_single_line?(node)
9
11
  !too_long?(node) &&
10
12
  !comment_within?(node) &&
@@ -21,7 +23,7 @@ module RuboCop
21
23
  return false unless max_line_length
22
24
 
23
25
  lines = processed_source.lines[(line_range.begin - 1)...line_range.end]
24
- to_single_line(lines.join("\n")).length > max_line_length
26
+ line_length(to_single_line(lines.join("\n"))) > max_line_length
25
27
  end
26
28
 
27
29
  def to_single_line(source)
@@ -66,7 +66,7 @@ module RuboCop
66
66
  # the line at which the parent node ends.
67
67
  # rubocop:disable-next Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
68
68
  def find_end_line(node)
69
- if node.if_type?
69
+ if node.if_type? && !node.modifier_form?
70
70
  if node.else?
71
71
  node.loc.else.line
72
72
  elsif node.ternary?
@@ -45,12 +45,44 @@ module RuboCop
45
45
 
46
46
  def command_disallowed_in_endless_body?(body)
47
47
  return true if body.assignment? && command_call?(assigned_value(body))
48
+ return true if body_disallowed_in_argument?(body)
49
+ return true if negated_command_call?(body)
48
50
 
49
51
  body.each_node(:any_block).any? do |block|
50
52
  block.keywords? && command_call?(block.send_node)
51
53
  end
52
54
  end
53
55
 
56
+ def body_disallowed_in_argument?(body)
57
+ command_body_in_argument?(body) || do_end_block_body_in_command_argument?(body)
58
+ end
59
+
60
+ # `private def foo = bar baz` parses only on Ruby 4.0+, and only as the sole argument.
61
+ def command_body_in_argument?(body)
62
+ return false unless command_call?(body)
63
+
64
+ call = argument_receiving_call(body.parent)
65
+ return false if call.nil?
66
+
67
+ target_ruby_version < 4.0 || !call.arguments.one?
68
+ end
69
+
70
+ def argument_receiving_call(def_node)
71
+ call = def_node.parent
72
+ return unless call&.call_type?
73
+
74
+ call if call.arguments.any? { |argument| argument.equal?(def_node) }
75
+ end
76
+
77
+ def do_end_block_body_in_command_argument?(body)
78
+ call = argument_receiving_call(body.parent)
79
+ return false if call.nil? || call.parenthesized?
80
+
81
+ body.each_node(:any_block).any? do |block|
82
+ block.keywords? && !block.send_node.lambda_literal?
83
+ end
84
+ end
85
+
54
86
  def assigned_value(node)
55
87
  while node.assignment? || node.rescue_type?
56
88
  node = node.rescue_type? ? node.body : node.children.last
@@ -64,6 +96,10 @@ module RuboCop
64
96
 
65
97
  node.arguments? && !node.parenthesized? && !node.operator_method? && !node.setter_method?
66
98
  end
99
+
100
+ def negated_command_call?(node)
101
+ node.send_type? && node.prefix_bang? && command_call?(node.receiver)
102
+ end
67
103
  end
68
104
  end
69
105
  end
@@ -55,13 +55,21 @@ module RuboCop
55
55
  # Common functionality for checking alignment of hash values.
56
56
  module ValueAlignment
57
57
  def checkable_layout?(node)
58
- !node.pairs_on_same_line? && !node.mixed_delimiters? && alignable_values?(node)
58
+ !node.pairs_on_same_line? && !node.mixed_delimiters? && alignable_values?(node) &&
59
+ !pair_after_kwsplat_on_same_line?(node)
59
60
  end
60
61
 
62
+ # With several enforced styles, deltas are computed even for a hash this style
63
+ # can't check. A pair that doesn't begin its line can't be moved without
64
+ # deleting whatever precedes it, so it's left alone.
61
65
  def deltas(first_pair, current_pair)
66
+ return {} unless current_pair.equal?(first_pair) ||
67
+ Util.begins_its_line?(current_pair.source_range)
68
+
62
69
  key_delta = key_delta(first_pair, current_pair)
63
- separator_delta = separator_delta(first_pair, current_pair, key_delta)
64
- value_delta = value_delta(first_pair, current_pair) - key_delta - separator_delta
70
+ shift = separator_shift(current_pair, key_delta)
71
+ separator_delta = separator_delta(first_pair, current_pair, shift)
72
+ value_delta = value_delta(first_pair, current_pair) - shift - separator_delta
65
73
 
66
74
  { key: key_delta, separator: separator_delta, value: value_delta }
67
75
  end
@@ -78,9 +86,22 @@ module RuboCop
78
86
  pair.value && pair.key.last_line != pair.value.first_line
79
87
  end
80
88
 
81
- def separator_delta(first_pair, current_pair, key_delta)
89
+ def pair_after_kwsplat_on_same_line?(node)
90
+ first_pair = node.pairs.first
91
+
92
+ node.children.each_cons(2).any? do |previous, current|
93
+ current.pair_type? && !current.equal?(first_pair) &&
94
+ previous.last_line == current.first_line
95
+ end
96
+ end
97
+
98
+ def separator_shift(current_pair, key_delta)
99
+ current_pair.key.multiline? ? 0 : key_delta
100
+ end
101
+
102
+ def separator_delta(first_pair, current_pair, shift)
82
103
  if current_pair.hash_rocket?
83
- hash_rocket_delta(first_pair, current_pair) - key_delta
104
+ hash_rocket_delta(first_pair, current_pair) - shift
84
105
  else
85
106
  0
86
107
  end
@@ -7,6 +7,8 @@ module RuboCop
7
7
  include LineLengthHelp
8
8
  include AllowedPattern
9
9
 
10
+ SPACE_INSIDE_PARENS_STYLES = %w[space compact].freeze
11
+
10
12
  private
11
13
 
12
14
  def single_line_as_modifier?(node)
@@ -42,7 +44,10 @@ module RuboCop
42
44
  def modifier_fits_on_single_line?(node)
43
45
  return true unless max_line_length
44
46
 
45
- line_length(line_in_modifier_form(node)) <= max_line_length
47
+ length = line_length(line_in_modifier_form(node))
48
+ length += 2 if parenthesize?(node) && space_inside_parens?
49
+
50
+ length <= max_line_length
46
51
  end
47
52
 
48
53
  def line_in_modifier_form(node)
@@ -52,6 +57,14 @@ module RuboCop
52
57
  "#{code_before}#{to_modifier_form(node)}#{code_after(node)}"
53
58
  end
54
59
 
60
+ def space_inside_parens?
61
+ return false unless config.cop_enabled?('Layout/SpaceInsideParens')
62
+
63
+ style = config.for_cop('Layout/SpaceInsideParens')['EnforcedStyle']
64
+
65
+ SPACE_INSIDE_PARENS_STYLES.include?(style)
66
+ end
67
+
55
68
  # Whether an existing line is acceptable to `Layout/LineLength`,
56
69
  # exemptions (the cop disabled at that line, an allowed pattern, an
57
70
  # allowed cop directive, an allowed URI) included. Only for judging
@@ -98,7 +111,7 @@ module RuboCop
98
111
 
99
112
  # rubocop:disable-next Metrics/AbcSize
100
113
  def if_body_source(if_body)
101
- if if_body.call_type? && !if_body.method?(:[]=) && omitted_value_in_last_hash_arg?(if_body)
114
+ if unparenthesized_call_with_omitted_value?(if_body)
102
115
  "#{method_source(if_body)}(#{if_body.arguments.map(&:source).join(', ')})"
103
116
  elsif (value = wrapped_value(if_body))
104
117
  prefix = if_body.source_range.begin.join(value.source_range.begin).source
@@ -110,7 +123,8 @@ module RuboCop
110
123
  end
111
124
  end
112
125
 
113
- def omitted_value_in_last_hash_arg?(if_body)
126
+ def unparenthesized_call_with_omitted_value?(if_body)
127
+ return false if !if_body.call_type? || if_body.method?(:[]=) || if_body.parenthesized?
114
128
  return false unless (last_argument = if_body.last_argument)
115
129
 
116
130
  last_argument.hash_type? && last_argument.pairs.last&.value_omission?
@@ -163,7 +163,7 @@ module RuboCop
163
163
 
164
164
  def put_comma(items, kind)
165
165
  last_item = items.last
166
- return if last_item.block_pass_type?
166
+ return if last_item.type?(:block_pass, :forwarded_args)
167
167
 
168
168
  range = autocorrect_range(last_item)
169
169
  msg = format(MSG, command: 'Put a', unit: format(kind, article: 'a multiline'))
@@ -63,9 +63,10 @@ module RuboCop
63
63
  (send nil? VISIBILITY_SCOPES def)
64
64
  PATTERN
65
65
 
66
+ # A modifier applies to every symbol it is given, not only to a sole argument.
66
67
  # @!method visibility_inline_on_method_name?(node, method_name:)
67
68
  def_node_matcher :visibility_inline_on_method_name?, <<~PATTERN
68
- (send nil? VISIBILITY_SCOPES (sym %method_name))
69
+ (send nil? VISIBILITY_SCOPES <(sym %method_name) ...>)
69
70
  PATTERN
70
71
  end
71
72
  end
@@ -128,7 +128,7 @@ module RuboCop
128
128
  if child_node.lvar_type?
129
129
  parent_node = child_node.parent
130
130
  if parent_node.respond_to?(:value_omission?) && parent_node.value_omission?
131
- corrector.insert_after(parent_node.loc.operator, " #{preferred_name}")
131
+ corrector.replace(parent_node.loc.operator, ": #{preferred_name}")
132
132
  else
133
133
  corrector.replace(child_node, preferred_name)
134
134
  end
@@ -132,10 +132,11 @@ module RuboCop
132
132
  # private alias_method :qux, :foo
133
133
  #
134
134
  # end
135
- class AccessModifierDeclarations < Base
135
+ class AccessModifierDeclarations < Base # rubocop:disable Metrics/ClassLength
136
136
  extend AutoCorrector
137
137
 
138
138
  include ConfigurableEnforcedStyle
139
+ include EndlessMethodBodyHelp
139
140
  include RangeHelp
140
141
 
141
142
  GROUP_STYLE_MESSAGE = [
@@ -169,6 +170,10 @@ module RuboCop
169
170
  (send nil? :alias_method _ _))
170
171
  PATTERN
171
172
 
173
+ def self.autocorrect_incompatible_with
174
+ [Style::EndlessMethod, Style::SingleLineMethods]
175
+ end
176
+
172
177
  def on_send(node)
173
178
  return if allowed?(node)
174
179
 
@@ -248,10 +253,33 @@ module RuboCop
248
253
 
249
254
  access_modifier_is_inlined?(node) && !right_siblings_same_inline_method?(node)
250
255
  else
251
- access_modifier_is_not_inlined?(node) && select_grouped_def_nodes(node).any?
256
+ access_modifier_is_not_inlined?(node) && inlinable?(select_grouped_def_nodes(node))
252
257
  end
253
258
  end
254
259
 
260
+ def inlinable?(def_nodes)
261
+ return false if def_nodes.empty?
262
+
263
+ def_nodes.none? { |def_node| def_node.endless? && body_unsafe_to_inline?(def_node.body) }
264
+ end
265
+
266
+ def body_unsafe_to_inline?(body)
267
+ return true if body.each_node(:any_block).any? { |block| do_end_block?(block) }
268
+
269
+ target_ruby_version < 4.0 && command_body?(body)
270
+ end
271
+
272
+ def do_end_block?(block)
273
+ block.keywords? && !block.send_node.lambda_literal?
274
+ end
275
+
276
+ def command_body?(body)
277
+ body = body.body while body.rescue_type?
278
+ return true if body.send_type? && body.prefix_not? && !body.parenthesized?
279
+
280
+ command_call?(body)
281
+ end
282
+
255
283
  def correctable_group_offense?(node)
256
284
  return false unless group_style?
257
285
  return false if allowed?(node)