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
@@ -146,11 +146,26 @@ module RuboCop
146
146
  end
147
147
 
148
148
  def comment_before_to_f?(node)
149
+ return true if comment_around_fdiv_operator?(node)
150
+
149
151
  operands_losing_to_f(node).any? do |operand|
150
152
  next false unless to_f_method?(operand)
151
153
 
152
154
  range = operand.receiver.source_range.end.join(operand.loc.selector.begin)
153
- processed_source.contains_comment?(range)
155
+ comment_in_range?(range)
156
+ end
157
+ end
158
+
159
+ def comment_around_fdiv_operator?(node)
160
+ return false unless style == :fdiv
161
+
162
+ range = node.receiver.source_range.end.join(node.first_argument.source_range.begin)
163
+ comment_in_range?(range)
164
+ end
165
+
166
+ def comment_in_range?(range)
167
+ processed_source.each_comment_in_lines(range.line..range.last_line).any? do |comment|
168
+ range.contains?(comment.source_range)
154
169
  end
155
170
  end
156
171
 
@@ -56,8 +56,10 @@ module RuboCop
56
56
 
57
57
  # `Style/RedundantBegin` removes the `begin` of a block body in the same pass,
58
58
  # leaving a `rescue` or `ensure` clause directly in the `for` body.
59
+ # `Style/ItBlockParameter` replaces block parameter references in the body
60
+ # with `it` in the same pass, while they become `for` loop variables.
59
61
  def self.autocorrect_incompatible_with
60
- [Style::RedundantBegin]
62
+ [Style::RedundantBegin, Style::ItBlockParameter]
61
63
  end
62
64
 
63
65
  def on_for(node)
@@ -78,6 +80,7 @@ module RuboCop
78
80
 
79
81
  if style == :for
80
82
  return unless node.receiver
83
+ return unless convertible_to_for?(node)
81
84
  return if rescue_or_ensure_body?(node) || do_end_block_in_collection?(node)
82
85
 
83
86
  add_offense(node, message: PREFER_FOR) do |corrector|
@@ -123,6 +126,16 @@ module RuboCop
123
126
  node.multiline? && node.method?(:each) && !node.send_node.arguments?
124
127
  end
125
128
 
129
+ def convertible_to_for?(node)
130
+ return false if node.type?(:numblock, :itblock)
131
+ return true unless node.arguments?
132
+
133
+ arguments = node.arguments
134
+ return false if arguments.children.one? && arguments.children.first.restarg_type?
135
+
136
+ arguments.each_descendant.all? { |arg| arg.type?(:arg, :restarg, :mlhs) }
137
+ end
138
+
126
139
  def rescue_or_ensure_body?(node)
127
140
  node.body&.type?(:rescue, :ensure)
128
141
  end
@@ -186,40 +186,37 @@ module RuboCop
186
186
  return if trivial?(node)
187
187
 
188
188
  example = "#{conditional_keyword} #{condition.source}; #{scope_exiting_keyword}; end"
189
- replacement = <<~RUBY.chomp
190
- #{conditional_keyword} #{condition.source}
191
- #{scope_exiting_keyword}
192
- end
193
- RUBY
189
+ replacement = "#{conditional_keyword} #{condition.source}\n #{scope_exiting_keyword}"
194
190
  end
195
191
 
196
192
  add_offense(node.loc.keyword, message: format(MSG, example: example)) do |corrector|
197
193
  next if node.else? && guard.nil?
198
194
 
199
- autocorrect(corrector, node, condition, replacement || example, guard)
195
+ autocorrect(corrector, node, example, replacement, guard)
200
196
  end
201
197
  end
202
198
 
203
199
  # rubocop:disable-next Metrics/AbcSize
204
- def autocorrect(corrector, node, condition, replacement, guard)
205
- corrector.replace(node.loc.keyword.join(condition.source_range), replacement)
206
-
207
- if_branch = node.if_branch
208
- else_branch = node.else_branch
209
-
200
+ def autocorrect(corrector, node, example, multiline_replacement, guard)
201
+ range = node.loc.keyword.join(node.condition.source_range)
202
+ corrector.replace(range, multiline_replacement || example)
210
203
  corrector.replace(node.loc.begin, "\n") if node.then?
211
204
 
212
- if (if_heredoc = find_heredoc_argument(if_branch))
213
- autocorrect_heredoc_argument(corrector, node, if_heredoc, else_branch, guard)
214
- elsif (else_heredoc = find_heredoc_argument(else_branch))
215
- autocorrect_heredoc_argument(corrector, node, else_heredoc, if_branch, guard)
216
- else
217
- corrector.remove(node.loc.end)
218
- return unless node.else?
205
+ branches = [node.if_branch, node.else_branch]
206
+ guard_branch, leave_branch = guard == :else ? branches.reverse : branches
219
207
 
220
- corrector.remove(node.loc.else)
221
- corrector.remove(range_of_branch_to_remove(node, guard))
208
+ heredoc = find_heredoc_argument(guard_branch)
209
+ close_multiline_guard_clause(corrector, range, heredoc, guard) if multiline_replacement
210
+
211
+ if heredoc
212
+ return autocorrect_heredoc_argument(corrector, node, heredoc, leave_branch, guard)
222
213
  end
214
+
215
+ corrector.remove(node.loc.end)
216
+ return unless node.else?
217
+
218
+ corrector.remove(node.loc.else)
219
+ corrector.remove(range_of_branch_to_remove(node, guard))
223
220
  end
224
221
 
225
222
  def heredoc?(argument)
@@ -241,6 +238,10 @@ module RuboCop
241
238
  find_heredoc_argument(node.receiver)
242
239
  end
243
240
 
241
+ def close_multiline_guard_clause(corrector, range, heredoc, guard)
242
+ corrector.insert_after(guard && heredoc ? heredoc.loc.heredoc_end : range, "\nend")
243
+ end
244
+
244
245
  def autocorrect_heredoc_argument(corrector, node, heredoc_node, leave_branch, guard)
245
246
  remove_whole_lines(corrector, node.loc.end)
246
247
  return unless node.else?
@@ -44,6 +44,10 @@ module RuboCop
44
44
  include ConfigurableEnforcedStyle
45
45
  extend AutoCorrector
46
46
 
47
+ def self.autocorrect_incompatible_with
48
+ [Layout::MultilineArrayBraceLayout, Style::TrailingCommaInArrayLiteral]
49
+ end
50
+
47
51
  def on_hash(node)
48
52
  return if node.children.first&.kwsplat_type?
49
53
  return unless (array = containing_array(node))
@@ -90,9 +90,19 @@ module RuboCop
90
90
  end
91
91
 
92
92
  def exempt_from_brackets?(node)
93
+ return true if forwarded_or_splatted?(node.first_argument)
94
+
93
95
  comment_before_dot?(node) || env_fetch_preferred?(node.receiver)
94
96
  end
95
97
 
98
+ def forwarded_or_splatted?(argument)
99
+ if argument.hash_type? && !argument.braces?
100
+ argument.children.any? { |child| child.type?(:kwsplat, :forwarded_kwrestarg) }
101
+ else
102
+ argument.type?(:forwarded_args, :forwarded_restarg, :splat, :block_pass)
103
+ end
104
+ end
105
+
96
106
  def comment_before_dot?(node)
97
107
  processed_source.contains_comment?(node.receiver.source_range.end.join(node.loc.dot))
98
108
  end
@@ -100,6 +100,7 @@ module RuboCop
100
100
  pattern_matching_nodes(condition).any?
101
101
 
102
102
  return unless (msg = message(node))
103
+ return if modifier_sharing_line_in_logical_operator?(node)
103
104
 
104
105
  add_offense(node.loc.keyword, message: format(msg, keyword: node.keyword)) do |corrector|
105
106
  next if part_of_ignored_node?(node)
@@ -167,11 +168,11 @@ module RuboCop
167
168
  return to_modifier_form_with_move_comment(node, indent(node), comment)
168
169
  end
169
170
 
170
- heredoc_ranges = heredoc_ranges_in(node.if_branch)
171
+ heredoc_ranges = heredoc_ranges_in(node)
171
172
  unless heredoc_ranges.empty?
172
173
  remove_heredoc(corrector, heredoc_ranges)
173
174
 
174
- return to_normal_form_with_heredoc(node, indent(node), heredoc_ranges)
175
+ return to_normal_form_with_heredoc(node, indent(node))
175
176
  end
176
177
 
177
178
  to_normal_form(node, indent(node))
@@ -233,11 +234,18 @@ module RuboCop
233
234
  end
234
235
 
235
236
  def collection_from_ancestor(node)
236
- return node if node&.type?(:array, :call)
237
+ return node if node&.type?(:array, :call, :and, :or)
237
238
 
238
239
  node.parent if node&.type?(:pair)
239
240
  end
240
241
 
242
+ def modifier_sharing_line_in_logical_operator?(node)
243
+ collection = find_containing_collection(node)
244
+ return false if !node.modifier_form? || !collection&.operator_keyword?
245
+
246
+ another_modifier_if_on_same_line?(node)
247
+ end
248
+
241
249
  def another_modifier_if_on_same_line?(node)
242
250
  collection = find_containing_collection(node)
243
251
  return false unless collection
@@ -280,15 +288,14 @@ module RuboCop
280
288
  end
281
289
  end
282
290
 
283
- def to_normal_form_with_heredoc(node, indentation, heredoc_ranges)
284
- heredoc_lines = heredoc_ranges.map(&:source)
285
-
286
- <<~RUBY.chomp
287
- #{node.keyword} #{node.condition.source}
288
- #{indentation} #{node.body.source}
289
- #{heredoc_lines.join("\n")}
290
- #{indentation}end
291
- RUBY
291
+ def to_normal_form_with_heredoc(node, indentation)
292
+ [
293
+ "#{node.keyword} #{node.condition.source}",
294
+ *heredoc_ranges_in(node.condition).map(&:source),
295
+ "#{indentation} #{node.body.source}",
296
+ *heredoc_ranges_in(node.if_branch).map(&:source),
297
+ "#{indentation}end"
298
+ ].join("\n")
292
299
  end
293
300
 
294
301
  def to_modifier_form_with_move_comment(node, indentation, comment)
@@ -59,6 +59,8 @@ module RuboCop
59
59
  MSG_AVOID_IT_PARAMETER = 'Avoid using `it` block parameter.'
60
60
  MSG_AVOID_IT_PARAMETER_MULTILINE = 'Avoid using `it` block parameter for multi-line blocks.'
61
61
 
62
+ SCOPE_TYPES = %i[any_def class module sclass].freeze
63
+
62
64
  minimum_target_ruby_version 3.4
63
65
 
64
66
  # `Style/StabbyLambdaParentheses` wraps the parameter this cop removes in the same pass,
@@ -67,6 +69,10 @@ module RuboCop
67
69
  [Style::Lambda, Style::StabbyLambdaParentheses]
68
70
  end
69
71
 
72
+ def on_new_investigation
73
+ @it_variable_definitions = nil
74
+ end
75
+
70
76
  def on_block(node)
71
77
  return unless style == :always
72
78
  return unless node.arguments.one?
@@ -75,7 +81,7 @@ module RuboCop
75
81
  return unless node.first_argument.arg_type?
76
82
 
77
83
  block_argument_name = node.first_argument.source
78
- return if used_within_inner_block?(node.body, block_argument_name)
84
+ return if keep_parameter_name?(node.body, block_argument_name)
79
85
 
80
86
  find_block_variables(node, block_argument_name).each do |variable|
81
87
  add_offense(variable, message: MSG_USE_IT_PARAMETER) do |corrector|
@@ -116,7 +122,7 @@ module RuboCop
116
122
  private
117
123
 
118
124
  def find_block_variables(node, block_argument_name)
119
- return [] unless node.body
125
+ return [] if !node.body || it_variable_in_scope?(node)
120
126
 
121
127
  ranges = inner_block_ranges(node.body)
122
128
 
@@ -125,10 +131,47 @@ module RuboCop
125
131
  end
126
132
  end
127
133
 
134
+ def it_variable_in_scope?(node)
135
+ scope = nearest_scope(node)
136
+ return true if it_argument_in_scope?(node, scope)
137
+
138
+ it_variable_definitions.any? do |definition|
139
+ nearest_scope(definition).equal?(scope) && visible_in_block?(definition, node)
140
+ end
141
+ end
142
+
143
+ def it_variable_definitions
144
+ @it_variable_definitions ||=
145
+ variable_definitions(processed_source.ast).filter_map do |name, definition|
146
+ definition if name == 'it'
147
+ end
148
+ end
149
+
150
+ def nearest_scope(node)
151
+ node.each_ancestor(*SCOPE_TYPES).first
152
+ end
153
+
154
+ def it_argument_in_scope?(node, scope)
155
+ owners = node.each_ancestor(:block).select { |block| nearest_scope(block).equal?(scope) }
156
+ owners << scope if scope&.any_def_type?
157
+
158
+ owners.any? { |owner| owner.arguments.argument_list.any? { |arg| arg.name == :it } }
159
+ end
160
+
161
+ def visible_in_block?(definition, node)
162
+ return true if definition.source_range.begin_pos < node.source_range.begin_pos
163
+
164
+ node.source_range.contains?(definition.source_range)
165
+ end
166
+
128
167
  def inner_block_ranges(body)
129
168
  body.each_node(:any_block).map { |block| block.loc.begin.join(block.loc.end) }
130
169
  end
131
170
 
171
+ def keep_parameter_name?(body, block_arg_name)
172
+ used_within_inner_block?(body, block_arg_name) || reassigned?(body, block_arg_name)
173
+ end
174
+
132
175
  def used_within_inner_block?(body, block_argument_name)
133
176
  return false unless body
134
177
 
@@ -139,11 +182,29 @@ module RuboCop
139
182
  end
140
183
  end
141
184
 
185
+ def reassigned?(body, block_argument_name)
186
+ return false unless body
187
+
188
+ variable_definitions(body).any? { |name, _definition| name == block_argument_name }
189
+ end
190
+
142
191
  def block_variables(body, block_argument_name)
143
192
  body.each_descendant(:lvar).select do |descendant|
144
193
  descendant.source == block_argument_name
145
194
  end
146
195
  end
196
+
197
+ def variable_definitions(node)
198
+ node.each_node(:lvasgn, :match_var, :match_with_lvasgn).flat_map do |definition|
199
+ names = if definition.match_with_lvasgn_type?
200
+ definition.children.first.each_capture(named: true).map(&:name)
201
+ else
202
+ [definition.children.first.to_s]
203
+ end
204
+
205
+ names.map { |name| [name, definition] }
206
+ end
207
+ end
147
208
  end
148
209
  end
149
210
  end
@@ -129,12 +129,27 @@ module RuboCop
129
129
 
130
130
  return unless node.arguments?
131
131
 
132
- arg_str = "(#{lambda_arg_string(node.arguments)})"
132
+ arg_str = lambda_arg_string(node.arguments)
133
+ arg_str = " #{arg_str} " if space_inside_parens?
134
+ arg_str = "(#{arg_str})"
135
+ arg_str = " #{arg_str}" if space_in_lambda_literal_required?
133
136
 
134
137
  corrector.insert_after(node.send_node, arg_str)
135
138
  corrector.remove(arguments_with_whitespace(node))
136
139
  end
137
140
 
141
+ def space_inside_parens?
142
+ return false unless config.cop_enabled?('Layout/SpaceInsideParens')
143
+
144
+ %w[space compact].include?(config.for_cop('Layout/SpaceInsideParens')['EnforcedStyle'])
145
+ end
146
+
147
+ def space_in_lambda_literal_required?
148
+ return false unless config.cop_enabled?('Layout/SpaceInLambdaLiteral')
149
+
150
+ config.for_cop('Layout/SpaceInLambdaLiteral')['EnforcedStyle'] == 'require_space'
151
+ end
152
+
138
153
  def arguments_with_whitespace(node)
139
154
  node.loc.begin.end.join(node.arguments.loc.end)
140
155
  end
@@ -30,7 +30,7 @@ module RuboCop
30
30
 
31
31
  private
32
32
 
33
- def omit_parentheses(node) # rubocop:disable Metrics/PerceivedComplexity
33
+ def omit_parentheses(node) # rubocop:disable Metrics/AbcSize, Metrics/PerceivedComplexity
34
34
  return unless node.parenthesized?
35
35
  return if inside_endless_method_def?(node)
36
36
  return if require_parentheses_for_hash_value_omission?(node)
@@ -42,6 +42,7 @@ module RuboCop
42
42
  return if allowed_string_interpolation_method_call?(node)
43
43
  return if code_after_closing_parenthesis_on_its_own_line?(node)
44
44
  return if comment_before_first_argument?(node)
45
+ return if args_parenthesized?(node)
45
46
 
46
47
  (@pending_omit_offenses ||= []) << node
47
48
  end
@@ -132,7 +133,7 @@ module RuboCop
132
133
  opening.source_line[(opening.column + 1)..].gsub(TRAILING_WHITESPACE_REGEX, '').empty?
133
134
  end
134
135
 
135
- def legitimate_call_with_parentheses?(node) # rubocop:disable Metrics/PerceivedComplexity
136
+ def legitimate_call_with_parentheses?(node) # rubocop:disable Metrics/AbcSize, Metrics/PerceivedComplexity
136
137
  call_in_literals?(node) ||
137
138
  node.parent&.when_type? ||
138
139
  call_with_ambiguous_arguments?(node) ||
@@ -142,7 +143,9 @@ module RuboCop
142
143
  allowed_multiline_call_with_parentheses?(node) ||
143
144
  allowed_chained_call_with_parentheses?(node) ||
144
145
  assignment_in_condition?(node) ||
145
- forwards_anonymous_rest_arguments?(node)
146
+ command_assignment_in_expression?(node) ||
147
+ forwards_anonymous_rest_arguments?(node) ||
148
+ trailing_comma?(node)
146
149
  end
147
150
 
148
151
  def call_in_literals?(node)
@@ -278,7 +281,7 @@ module RuboCop
278
281
  end
279
282
 
280
283
  def assigned_before?(node, target)
281
- node.assignment? && node.loc.operator.begin < target.loc.begin
284
+ node.assignment? && node.loc.operator.begin < target.source_range.begin
282
285
  end
283
286
 
284
287
  def inside_string_interpolation?(node)
@@ -295,6 +298,27 @@ module RuboCop
295
298
  parent.assignment? && (grandparent.conditional? || grandparent.when_type?)
296
299
  end
297
300
 
301
+ # rubocop:disable-next Metrics/PerceivedComplexity
302
+ def command_assignment_in_expression?(node)
303
+ value = node.parent&.type?(:any_block, :rescue) ? node.parent : node
304
+ assignment = outermost_assignment(value)
305
+ return false if assignment.equal?(value)
306
+
307
+ call_in_literals?(assignment) ||
308
+ call_in_logical_operators?(assignment) ||
309
+ call_in_optional_arguments?(assignment) ||
310
+ call_as_argument_or_chain?(assignment) ||
311
+ assignment.parent&.type?(:masgn, :return, :next, :break, :defined?)
312
+ end
313
+
314
+ def outermost_assignment(value)
315
+ parent = value.parent
316
+ return value if !parent&.assignment? || parent.masgn_type?
317
+ return value unless assigned_before?(parent, value)
318
+
319
+ outermost_assignment(parent)
320
+ end
321
+
298
322
  def forwards_anonymous_rest_arguments?(node)
299
323
  node.arguments.any? do |argument|
300
324
  next true if argument.forwarded_restarg_type?
@@ -302,6 +326,14 @@ module RuboCop
302
326
  argument.hash_type? && argument.children.any?(&:forwarded_kwrestarg_type?)
303
327
  end
304
328
  end
329
+
330
+ def trailing_comma?(node)
331
+ return false unless (last_argument = node.last_argument)
332
+
333
+ range = last_argument.source_range.end.join(node.loc.end.begin)
334
+
335
+ processed_source.tokens_within(range).any?(&:comma?)
336
+ end
305
337
  end
306
338
  end
307
339
  end
@@ -243,6 +243,7 @@ module RuboCop
243
243
 
244
244
  def self.autocorrect_incompatible_with
245
245
  [
246
+ Layout::HeredocArgumentClosingParenthesis,
246
247
  Style::NestedParenthesizedCalls,
247
248
  Style::RedundantParentheses,
248
249
  Style::RescueModifier,
@@ -169,12 +169,20 @@ module RuboCop
169
169
  location = node.arguments.source_range
170
170
 
171
171
  add_offense(location, message: MSG_MISSING) do |corrector|
172
- add_parentheses(node.arguments, corrector)
172
+ corrector.replace(range_before_arguments(node), '(')
173
+ corrector.insert_after(location, ')')
173
174
 
174
175
  unexpected_style_detected 'require_no_parentheses'
175
176
  end
176
177
  end
177
178
 
179
+ def range_before_arguments(node)
180
+ range = node.loc.name.end.join(node.arguments.source_range.begin)
181
+ last_line_break = range.source.rindex("\n")
182
+
183
+ last_line_break ? range.resize(last_line_break) : range
184
+ end
185
+
178
186
  def unwanted_parentheses(args)
179
187
  add_offense(args, message: MSG_PRESENT) do |corrector|
180
188
  # offense is registered on args node when parentheses are unwanted
@@ -117,17 +117,26 @@ module RuboCop
117
117
 
118
118
  def separate_mixins(node)
119
119
  arguments = node.arguments.reverse
120
- mixins = ["#{node.method_name} #{arguments.first.source}"]
120
+ mixins = [mixin_call(node.method_name, arguments.first.source, node.parenthesized?)]
121
121
 
122
122
  arguments[1..].inject(mixins) do |replacement, arg|
123
- replacement << "#{indent(node)}#{node.method_name} #{arg.source}"
123
+ call = mixin_call(node.method_name, arg.source, node.parenthesized?)
124
+ replacement << "#{indent(node)}#{call}"
124
125
  end.join("\n")
125
126
  end
126
127
 
127
128
  def group_mixins(node, mixins)
128
129
  mixin_names = mixins.reverse.flat_map { |mixin| mixin.arguments.map(&:source) }
129
130
 
130
- "#{node.method_name} #{mixin_names.join(', ')}"
131
+ mixin_call(node.method_name, mixin_names.join(', '), mixins.any?(&:parenthesized?))
132
+ end
133
+
134
+ def mixin_call(method_name, arguments_source, parenthesized)
135
+ if parenthesized
136
+ "#{method_name}(#{arguments_source})"
137
+ else
138
+ "#{method_name} #{arguments_source}"
139
+ end
131
140
  end
132
141
  end
133
142
  end
@@ -150,7 +150,7 @@ module RuboCop
150
150
  # is the last element and spans multiple lines, the trailing comma lands between the literal
151
151
  # and the `.freeze` appended in the same pass.
152
152
  def self.autocorrect_incompatible_with
153
- [Style::TrailingCommaInArrayLiteral, Style::TrailingCommaInHashLiteral]
153
+ [Style::HashAsLastArrayItem, Style::TrailingCommaInArrayLiteral, Style::TrailingCommaInHashLiteral]
154
154
  end
155
155
 
156
156
  def on_casgn(node)
@@ -228,15 +228,32 @@ module RuboCop
228
228
  return
229
229
  end
230
230
 
231
+ return expand_omitted_hash_value(corrector, node.parent) if omitted_hash_value?(node)
232
+
233
+ wrap_for_freeze(corrector, node, expr)
234
+ corrector.insert_after(expr, '.freeze')
235
+
236
+ freeze_nested_literals(corrector, node) if recursive?
237
+ end
238
+
239
+ def omitted_hash_value?(node)
240
+ node.parent&.pair_type? && node.parent.value_omission?
241
+ end
242
+
243
+ def expand_omitted_hash_value(corrector, pair)
244
+ key = pair.key.value
245
+
246
+ corrector.replace(pair, "#{key}: #{key}.freeze")
247
+ end
248
+
249
+ def wrap_for_freeze(corrector, node, expr)
231
250
  if node.array_type? && !node.bracketed?
232
251
  corrector.wrap(expr, '[', ']')
252
+ elsif node.hash_type? && !node.braces?
253
+ corrector.wrap(expr, '{ ', ' }')
233
254
  elsif requires_parentheses?(node)
234
255
  corrector.wrap(expr, '(', ')')
235
256
  end
236
-
237
- corrector.insert_after(expr, '.freeze')
238
-
239
- freeze_nested_literals(corrector, node) if recursive?
240
257
  end
241
258
 
242
259
  # Recursively freezes every nested mutable literal inside an array or
@@ -262,13 +279,14 @@ module RuboCop
262
279
  # Returns the child literals of an array or hash node that may
263
280
  # themselves need freezing. For hashes, both keys and values are
264
281
  # included. Percent-literal arrays (e.g. `%w(a b)`) are skipped because
265
- # `.freeze` cannot be appended to their contents.
282
+ # `.freeze` cannot be appended to their contents. Splat elements are
283
+ # skipped because the splatted object is not stored in the array.
266
284
  def literal_children(node)
267
285
  case node.type
268
286
  when :array
269
287
  return [] if node.percent_literal?
270
288
 
271
- node.children
289
+ node.children.reject(&:splat_type?)
272
290
  when :hash
273
291
  node.children.flat_map { |child| child.pair_type? ? child.children : [] }
274
292
  else
@@ -99,7 +99,10 @@ module RuboCop
99
99
 
100
100
  message = format(MSG, prefer: replacement, current: node.source)
101
101
  add_offense(node, message: message) do |corrector|
102
+ next if inside_corrected_predicate?(node)
103
+
102
104
  corrector.replace(node, replacement)
105
+ ignore_node(node)
103
106
  end
104
107
  end
105
108
 
@@ -115,6 +118,10 @@ module RuboCop
115
118
  BitwisePredicate.support_target_ruby_version?(target_ruby_version)
116
119
  end
117
120
 
121
+ def inside_corrected_predicate?(node)
122
+ node.each_ancestor(:send).any? { |ancestor| ignored_node?(ancestor) }
123
+ end
124
+
118
125
  def check(node)
119
126
  numeric, operator =
120
127
  if style == :predicate
@@ -83,7 +83,8 @@ module RuboCop
83
83
  control_op_condition(cond) do |first_child, rest_children|
84
84
  return if require_parentheses?(node, first_child)
85
85
  return if semicolon_separated_expressions?(first_child, rest_children)
86
- return if modifier_op?(first_child)
86
+ return if modifier_op?(first_child) || first_child.masgn_type?
87
+ return if command_call_assignment?(first_child)
87
88
  return if parens_allowed?(cond)
88
89
 
89
90
  message = message(cond)
@@ -115,6 +116,23 @@ module RuboCop
115
116
  node.basic_conditional? && node.modifier_form?
116
117
  end
117
118
 
119
+ def command_call_assignment?(node)
120
+ return false if !node.assignment? && !setter_method?(node)
121
+
122
+ value = node.children.last
123
+ value = value.body if value.rescue_type?
124
+
125
+ command_call_assignment?(value) || command_call?(value)
126
+ end
127
+
128
+ def command_call?(node)
129
+ node = node.send_node if node.any_block_type?
130
+ return false if !node.type?(:call, :super, :yield) || !node.arguments?
131
+ return false if node.parenthesized? || node.operator_method? || node.setter_method?
132
+
133
+ !node.method?(:[])
134
+ end
135
+
118
136
  def message(node)
119
137
  kw = node.parent.keyword
120
138
  article = kw == 'while' ? 'a' : 'an'
@@ -86,14 +86,12 @@ module RuboCop
86
86
  end
87
87
 
88
88
  def correct_quotes(str)
89
- correction = if style == :single_quotes
90
- to_string_literal(str)
91
- else
92
- str.gsub("\\'", "'").inspect
93
- end
94
-
95
89
  # The conversion process doubles escaped slashes, so they have to be reverted
96
- correction.gsub('\\\\', '\\').gsub('\"', '"')
90
+ if style == :single_quotes
91
+ to_string_literal(str).gsub('\\\\', '\\').gsub('\"', '"')
92
+ else
93
+ str.gsub("\\'", "'").inspect.gsub('\\\\', '\\')
94
+ end
97
95
  end
98
96
 
99
97
  def style