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
@@ -85,7 +85,7 @@ module RuboCop
85
85
  /xi.freeze
86
86
 
87
87
  def self.autocorrect_incompatible_with
88
- [Style::MethodCallWithArgsParentheses]
88
+ [Style::MethodCallWithArgsParentheses, Style::MethodDefParentheses]
89
89
  end
90
90
 
91
91
  def on_block(node)
@@ -122,8 +122,10 @@ module RuboCop
122
122
  def on_new_investigation
123
123
  @breakable_range_by_line_index = {}
124
124
  @breakable_string_delimiters = {}
125
+ @breakable_strings = {}
125
126
  @endless_methods_by_line = {}
126
127
  @heredocs = nil
128
+ @heredoc_openers = nil
127
129
 
128
130
  return unless processed_source.raw_source.include?(';')
129
131
 
@@ -207,37 +209,69 @@ module RuboCop
207
209
  def check_for_breakable_node(node)
208
210
  breakable_node = extract_breakable_node(node, max)
209
211
  return if breakable_node.nil?
212
+ return if compact_nested_array_break?(node, breakable_node)
210
213
 
211
214
  line_index = breakable_node.first_line - 1
212
215
  range = breakable_node.source_range
213
216
 
214
217
  existing = breakable_range_by_line_index[line_index]
215
- return if existing
218
+ return if existing || after_heredoc_opener?(range)
216
219
 
217
220
  breakable_range_by_line_index[line_index] = range
218
221
  end
219
222
 
223
+ def after_heredoc_opener?(range)
224
+ heredoc_openers.any? do |opener|
225
+ opener.line == range.line && opener.begin_pos < range.begin_pos
226
+ end
227
+ end
228
+
229
+ def heredoc_openers
230
+ ast = processed_source.ast
231
+ return [] if ast.nil?
232
+
233
+ @heredoc_openers ||= ast.each_node(:any_str).select(&:heredoc?).map(&:source_range)
234
+ end
235
+
236
+ def compact_nested_array_break?(node, breakable_node)
237
+ return false if !node.array_type? || !node.square_brackets?
238
+ return false unless breakable_node.equal?(node.children.first)
239
+ return false unless breakable_node.source.start_with?('[')
240
+
241
+ compact_array_brackets_enforced?
242
+ end
243
+
244
+ def compact_array_brackets_enforced?
245
+ return false unless config.cop_enabled?('Layout/SpaceInsideArrayLiteralBrackets')
246
+
247
+ config.for_cop('Layout/SpaceInsideArrayLiteralBrackets')['EnforcedStyle'] == 'compact'
248
+ end
249
+
220
250
  def check_for_breakable_semicolons(processed_source)
221
251
  tokens = processed_source.tokens.select { |t| t.type == :tSEMI }
222
252
  tokens.reverse_each do |token|
223
253
  range = breakable_range_after_semicolon(token)
224
- breakable_range_by_line_index[range.line - 1] = range if range
254
+ next if range.nil? || after_heredoc_opener?(range)
255
+
256
+ breakable_range_by_line_index[range.line - 1] = range
225
257
  end
226
258
  end
227
259
 
228
260
  def check_for_breakable_block(block_node)
229
261
  return unless block_node.single_line?
230
- return if receiver_contains_heredoc?(block_node)
231
262
 
232
263
  line_index = block_node.loc.line - 1
233
264
  return if breakable_string_delimiters[line_index]
234
265
 
235
266
  range = breakable_block_range(block_node)
236
267
  pos = range.begin_pos + 1
268
+ range = range_between(pos, pos + 1)
269
+ return if after_heredoc_opener?(range)
237
270
 
238
- breakable_range_by_line_index[line_index] = range_between(pos, pos + 1)
271
+ breakable_range_by_line_index[line_index] = range
239
272
  end
240
273
 
274
+ # rubocop:disable-next Metrics/AbcSize
241
275
  def check_for_breakable_str(node)
242
276
  line_index = node.loc.line - 1
243
277
  return if breakable_range_by_line_index[line_index]
@@ -248,6 +282,7 @@ module RuboCop
248
282
 
249
283
  breakable_range_by_line_index[line_index] = range_between(pos, pos + 1)
250
284
  breakable_string_delimiters[line_index] = delimiter
285
+ breakable_strings[line_index] = node
251
286
  end
252
287
 
253
288
  def check_for_breakable_dstr(node) # rubocop:disable Metrics/AbcSize
@@ -271,7 +306,8 @@ module RuboCop
271
306
  !node.heredoc? &&
272
307
  # TODO: strings inside hashes, kwargs and arrays are currently ignored,
273
308
  # but could be considered in the future
274
- !node.parent&.type?(:pair, :kwoptarg, :array)
309
+ !node.parent&.type?(:pair, :kwoptarg, :array) &&
310
+ !after_heredoc_opener?(node.source_range)
275
311
  end
276
312
 
277
313
  def breakable_block_range(block_node)
@@ -301,7 +337,32 @@ module RuboCop
301
337
  return unless (pos = breakable_string_range(node))
302
338
 
303
339
  content_begin_pos = node.loc?(:begin) ? node.loc.begin.end_pos : source_range.begin_pos
304
- pos.end_pos if pos.end_pos > content_begin_pos
340
+ if blank_left_part?(node, content_begin_pos, pos)
341
+ pos = breakable_string_range_at_limit(source_range)
342
+ end
343
+ pos.end_pos if pos && leaves_two_parts?(node, content_begin_pos, pos)
344
+ end
345
+
346
+ def leaves_two_parts?(node, content_begin_pos, pos)
347
+ pos.end_pos > content_begin_pos && !blank_rest?(node, pos.end_pos)
348
+ end
349
+
350
+ def blank_left_part?(node, content_begin_pos, pos)
351
+ return false unless pos.end_pos > content_begin_pos
352
+ return false unless starts_after_quote?(node)
353
+
354
+ blank_between?(content_begin_pos, pos.end_pos)
355
+ end
356
+
357
+ def starts_after_quote?(node)
358
+ return true if node.loc?(:begin)
359
+
360
+ quote = node.parent&.loc&.begin if node.parent&.dstr_type?
361
+ quote ? quote.end_pos == node.source_range.begin_pos : false
362
+ end
363
+
364
+ def blank_between?(begin_pos, end_pos)
365
+ range_between(begin_pos, end_pos).source.strip.empty?
305
366
  end
306
367
 
307
368
  # Locate where to break a string that is too long, ensuring that escape characters
@@ -325,11 +386,17 @@ module RuboCop
325
386
 
326
387
  def breakable_space_position(node, substr)
327
388
  limit = string_content_length(node) - 1
328
- space_pos = substr.rindex(/\s/)
329
- space_pos = substr[0, limit].rindex(/\s/) if space_pos && space_pos + 1 > limit
389
+ space_pos = space_before_word(node, substr.length - 1)
390
+ space_pos = space_before_word(node, limit - 1) if space_pos && space_pos + 1 > limit
330
391
  space_pos
331
392
  end
332
393
 
394
+ def space_before_word(node, max_pos)
395
+ return if max_pos.negative?
396
+
397
+ node.source.rindex(/\s(?=\S)/, max_pos)
398
+ end
399
+
333
400
  def escape_sequence_start(text)
334
401
  text.match(ESCAPE_SEQUENCE_TAIL)&.begin(1)
335
402
  end
@@ -339,10 +406,25 @@ module RuboCop
339
406
  return if adjustment.abs > source_range.size
340
407
 
341
408
  cut = source_range.adjust(end_pos: adjustment)
409
+ cut = cut.resize(size_before_whitespace_run(source_range.source, cut.size))
342
410
  escape_pos = escape_sequence_start(cut.source)
411
+
343
412
  escape_pos ? source_range.resize(escape_pos) : cut
344
413
  end
345
414
 
415
+ def size_before_whitespace_run(text, size)
416
+ return size if size.zero?
417
+ return size unless text[size]&.match?(/\s/)
418
+
419
+ text.rindex(/\S/, size - 1) || size
420
+ end
421
+
422
+ def blank_rest?(node, break_pos)
423
+ offset = break_pos - node.source_range.begin_pos
424
+
425
+ node.source[offset...string_content_length(node)].strip.empty?
426
+ end
427
+
346
428
  def string_content_length(node)
347
429
  content_end = node.loc?(:end) ? node.loc.end.begin_pos : node.source_range.end_pos
348
430
 
@@ -360,7 +442,9 @@ module RuboCop
360
442
  def opens_string?(begin_node)
361
443
  quote = begin_node.parent.loc.begin
362
444
 
363
- quote && quote.end_pos == begin_node.source_range.begin_pos
445
+ return false unless quote
446
+
447
+ blank_between?(quote.end_pos, begin_node.source_range.begin_pos)
364
448
  end
365
449
 
366
450
  def breakable_range_by_line_index
@@ -375,6 +459,10 @@ module RuboCop
375
459
  @breakable_string_delimiters ||= {}
376
460
  end
377
461
 
462
+ def breakable_strings
463
+ @breakable_strings ||= {}
464
+ end
465
+
378
466
  def heredocs
379
467
  @heredocs ||= extract_heredocs(processed_source.ast)
380
468
  end
@@ -431,8 +519,36 @@ module RuboCop
431
519
  "\n"
432
520
  end
433
521
 
434
- corrector.insert_before(breakable_range, insertion) unless breakable_range.nil?
522
+ break_ranges(line_index).each do |range|
523
+ corrector.insert_before(range, insertion)
524
+ end
525
+ end
526
+ end
527
+
528
+ def break_ranges(line_index)
529
+ return [] if breakable_range.nil?
530
+ return [breakable_range] unless (node = breakable_strings[line_index])&.loc?(:end)
531
+
532
+ string_break_ranges(node, breakable_string_delimiters[line_index])
533
+ end
534
+
535
+ def string_break_ranges(node, delimiter)
536
+ ranges = [breakable_range]
537
+ pos = breakable_range.begin_pos
538
+
539
+ while (pos = next_string_break_position(node, pos, delimiter))
540
+ ranges << range_between(pos, pos + 1)
435
541
  end
542
+
543
+ ranges
544
+ end
545
+
546
+ def next_string_break_position(node, pos, delimiter)
547
+ rest = parse("#{delimiter}#{node.source_range.with(begin_pos: pos).source}").ast
548
+ return unless rest&.str_type?
549
+ return unless (rest_pos = breakable_string_position(rest))
550
+
551
+ pos + rest_pos - delimiter.length
436
552
  end
437
553
 
438
554
  def excess_range(uri_range, line, line_index)
@@ -106,11 +106,13 @@ module RuboCop
106
106
  ALWAYS_SAME_LINE_MESSAGE = 'Closing hash brace must be on the same ' \
107
107
  'line as the last hash element.'
108
108
 
109
- # Moving the closing brace while `Style/HashAsLastArrayItem` or
110
- # `Style/RedundantDoubleSplatHashBraces` removes the braces in the same pass leaves
111
- # the brace behind.
109
+ # Moving the closing brace after a method call value while
110
+ # `Layout/MultilineMethodCallBraceLayout` moves that call's closing parenthesis
111
+ # before a comment in the same pass places the brace after the comment.
112
+ # Moving it while `Style/HashAsLastArrayItem` or `Style/RedundantDoubleSplatHashBraces`
113
+ # removes the braces in the same pass leaves the brace behind.
112
114
  def self.autocorrect_incompatible_with
113
- [Style::HashAsLastArrayItem, Style::RedundantDoubleSplatHashBraces]
115
+ [Layout::MultilineMethodCallBraceLayout, Style::HashAsLastArrayItem, Style::RedundantDoubleSplatHashBraces]
114
116
  end
115
117
 
116
118
  def on_hash(node)
@@ -102,7 +102,7 @@ module RuboCop
102
102
  def autocorrect(corrector, node)
103
103
  if @send_node.block_node
104
104
  correct_selector_only(corrector, node)
105
- correct_block(corrector, @send_node.block_node)
105
+ correct_block(corrector, @send_node.block_node, node.first_line)
106
106
  else
107
107
  AlignmentCorrector.correct(corrector, processed_source, node, @column_delta)
108
108
  end
@@ -114,10 +114,21 @@ module RuboCop
114
114
  AlignmentCorrector.correct(corrector, processed_source, selector_range, @column_delta)
115
115
  end
116
116
 
117
- def correct_block(corrector, block_node)
118
- AlignmentCorrector.correct(corrector, processed_source, block_node.body, @column_delta)
119
- end_range = range_by_whole_lines(block_node.loc.end, include_final_newline: false)
120
- AlignmentCorrector.correct(corrector, processed_source, end_range, @column_delta)
117
+ def correct_block(corrector, block_node, selector_line)
118
+ lines_to_shift = block_lines(block_node) - [selector_line]
119
+
120
+ lines_to_shift.each do |line|
121
+ line_range = processed_source.buffer.line_range(line)
122
+
123
+ AlignmentCorrector.correct(corrector, processed_source, line_range, @column_delta)
124
+ end
125
+ end
126
+
127
+ def block_lines(block_node)
128
+ body = block_node.body
129
+ lines = body ? (body.first_line..body.last_line).to_a : []
130
+
131
+ (lines << block_node.loc.end.line).uniq
121
132
  end
122
133
 
123
134
  def relevant_node?(send_node)
@@ -115,7 +115,7 @@ module RuboCop
115
115
  node.loc.name
116
116
  when :sclass
117
117
  node.identifier.source_range
118
- when :masgn
118
+ when :masgn, :op_asgn, :or_asgn, :and_asgn
119
119
  node.lhs.source_range
120
120
  else
121
121
  # It is a wrapper with receiver of object attribute or access modifier.
@@ -25,10 +25,10 @@ module RuboCop
25
25
 
26
26
  MSG = 'Put method call on a separate line if chained to a single line block.'
27
27
 
28
- # `Style/FloatDivision` and `Style/NilComparison` rewrite a method chained to the block
29
- # in the same pass, which must not start a line of its own.
28
+ # These cops rewrite a method chained to the block in the same pass,
29
+ # which must not start a line of its own.
30
30
  def self.autocorrect_incompatible_with
31
- [Style::FloatDivision, Style::MapToHash, Style::NilComparison]
31
+ [Style::FloatDivision, Style::MapToHash, Style::MapToSet, Style::NilComparison, Style::RedundantSort]
32
32
  end
33
33
 
34
34
  def on_send(node)
@@ -245,20 +245,30 @@ module RuboCop
245
245
  return if with_space.is?(operator.source)
246
246
 
247
247
  "Space around operator `#{operator.source}` detected."
248
- elsif !/^\s.*\s$/.match?(with_space.source)
248
+ elsif missing_surrounding_space?(with_space)
249
249
  "Surrounding space missing for operator `#{operator.source}`."
250
- elsif excess_leading_space?(type, operator, with_space) ||
250
+ elsif excess_leading_space?(type, operator, with_space, right_operand) ||
251
251
  excess_trailing_space?(right_operand.source_range, with_space)
252
252
  "Operator `#{operator.source}` should be surrounded " \
253
253
  'by a single space.'
254
254
  end
255
255
  end
256
256
 
257
- def excess_leading_space?(type, operator, with_space)
257
+ def missing_surrounding_space?(with_space)
258
+ return false if /\A\s.*\s\z/m.match?(with_space.source)
259
+
260
+ !with_space.source.start_with?(/\s/) || !line_continuation_after?(with_space)
261
+ end
262
+
263
+ def line_continuation_after?(range)
264
+ processed_source.buffer.source[range.end_pos, 2] == "\\\n"
265
+ end
266
+
267
+ def excess_leading_space?(type, operator, with_space, right_operand)
258
268
  return false unless allow_for_alignment?
259
269
  return false unless with_space.source.start_with?(EXCESSIVE_SPACE)
260
270
 
261
- return !aligned_with_operator?(operator) unless grouped_alignment_type?(type)
271
+ return !aligned?(type, operator, right_operand) unless grouped_alignment_type?(type)
262
272
 
263
273
  token = Token.new(operator, nil, operator.source)
264
274
  align_preceding = aligned_with_preceding_equals_operator(token)
@@ -269,6 +279,23 @@ module RuboCop
269
279
  aligned_with_subsequent_equals_operator(token) != :yes
270
280
  end
271
281
 
282
+ def aligned?(type, operator, right_operand)
283
+ return true if aligned_with_operator?(operator)
284
+
285
+ type == :pair && aligned_with_another_hash_rocket?(right_operand)
286
+ end
287
+
288
+ def aligned_with_another_hash_rocket?(pair)
289
+ hash_rocket_columns(pair.parent)[pair.loc.operator.column] > 1
290
+ end
291
+
292
+ def hash_rocket_columns(hash_node)
293
+ @hash_rocket_columns ||= {}.compare_by_identity
294
+ @hash_rocket_columns[hash_node] ||= hash_node.pairs.select(&:hash_rocket?).map do |pair|
295
+ pair.loc.operator.column
296
+ end.tally
297
+ end
298
+
272
299
  def grouped_alignment_type?(type)
273
300
  type == :assignment || (type == :special_asgn && force_equal_sign_alignment?)
274
301
  end
@@ -136,11 +136,23 @@ module RuboCop
136
136
 
137
137
  def autocorrect(corrector, range)
138
138
  case range.source
139
+ when /\n/ then remove_line_continuation(corrector, range)
139
140
  when /\s/ then corrector.remove(range)
140
141
  else corrector.insert_before(range, ' ')
141
142
  end
142
143
  end
143
144
 
145
+ # When a heredoc body sits between the `\` and the brace, the newline before
146
+ # the brace ends the heredoc, and removing it would break the terminator.
147
+ def remove_line_continuation(corrector, range)
148
+ source = processed_source.buffer.source
149
+ return unless source[range.begin_pos - 1] == '\\'
150
+
151
+ begin_pos = range.begin_pos
152
+ begin_pos -= 1 while source[begin_pos - 1].match?(/[ \t\\]/)
153
+ corrector.remove(range_between(begin_pos, range.end_pos))
154
+ end
155
+
144
156
  def style_for_empty_braces
145
157
  case cop_config['EnforcedStyleForEmptyBraces']
146
158
  when 'space' then :space
@@ -83,7 +83,7 @@ module RuboCop
83
83
  extend AutoCorrector
84
84
 
85
85
  def self.autocorrect_incompatible_with
86
- [Style::BlockDelimiters]
86
+ [Layout::BlockAlignment, Style::BlockDelimiters]
87
87
  end
88
88
 
89
89
  def on_block(node)
@@ -18,6 +18,7 @@ module RuboCop
18
18
  # # good
19
19
  # 'a'..'z'
20
20
  class SpaceInsideRangeLiteral < Base
21
+ include RangeHelp
21
22
  extend AutoCorrector
22
23
 
23
24
  MSG = 'Space inside range literal.'
@@ -33,21 +34,38 @@ module RuboCop
33
34
  private
34
35
 
35
36
  def check(node)
36
- expression = node.source
37
- op = node.loc.operator.source
38
- escaped_op = op.gsub('.', '\.')
39
-
40
- # account for multiline range literals
41
- expression.sub!(/#{escaped_op}\n\s*/, op)
42
-
43
- return unless /(\s#{escaped_op})|(#{escaped_op}\s)/.match?(expression)
37
+ operator = node.loc.operator
38
+ spaces = [space_before(node, operator), space_after(node, operator)].compact
39
+ return if spaces.all? { |space| line_break_after_operator?(space, operator) }
44
40
 
45
41
  add_offense(node) do |corrector|
46
- corrector.replace(
47
- node, expression.sub(/\s+#{escaped_op}/, op).sub(/#{escaped_op}\s+/, op)
48
- )
42
+ spaces.each do |space|
43
+ corrector.remove(space)
44
+ end
49
45
  end
50
46
  end
47
+
48
+ def space_before(node, operator)
49
+ return unless node.begin
50
+
51
+ whitespace_range(node.begin.source_range.end_pos, operator.begin_pos)
52
+ end
53
+
54
+ def space_after(node, operator)
55
+ return unless node.end
56
+
57
+ whitespace_range(operator.end_pos, node.end.source_range.begin_pos)
58
+ end
59
+
60
+ def line_break_after_operator?(space, operator)
61
+ space.begin_pos == operator.end_pos && space.source.start_with?("\n")
62
+ end
63
+
64
+ def whitespace_range(begin_pos, end_pos)
65
+ range = range_between(begin_pos, end_pos)
66
+
67
+ range if range.source.match?(/\A\s+\z/)
68
+ end
51
69
  end
52
70
  end
53
71
  end
@@ -108,14 +108,9 @@ module RuboCop
108
108
  end
109
109
 
110
110
  def left_ref_bracket(node, tokens)
111
- current_token = tokens.reverse.find(&:left_ref_bracket?)
112
- previous_token = previous_token(current_token)
111
+ bracket_pos = node.loc.selector.begin_pos
113
112
 
114
- if node.method?(:[]=) || (previous_token && !previous_token.right_bracket?)
115
- tokens.find(&:left_ref_bracket?)
116
- else
117
- current_token
118
- end
113
+ tokens.find { |token| token.left_ref_bracket? && token.begin_pos == bracket_pos }
119
114
  end
120
115
 
121
116
  def closing_bracket(tokens, opening_bracket)
@@ -129,11 +124,6 @@ module RuboCop
129
124
  end
130
125
  end
131
126
 
132
- def previous_token(current_token)
133
- index = processed_source.tokens.index(current_token)
134
- index.nil? || index.zero? ? nil : processed_source.tokens[index - 1]
135
- end
136
-
137
127
  def empty_config
138
128
  cop_config['EnforcedStyleForEmptyBrackets']
139
129
  end
@@ -55,7 +55,8 @@ module RuboCop
55
55
  return node if first_argument_is_regexp?(node) || !node.parent
56
56
 
57
57
  if (node.parent.send_type? && node.receiver) ||
58
- method_chain_to_regexp_receiver?(node, regexp_receiver)
58
+ method_chain_to_regexp_receiver?(node, regexp_receiver) ||
59
+ part_of_hash_argument?(node)
59
60
  node = find_offense_node(node.parent, regexp_receiver)
60
61
  end
61
62
 
@@ -72,6 +73,10 @@ module RuboCop
72
73
 
73
74
  parent.parent && parent_receiver.receiver == regexp_receiver
74
75
  end
76
+
77
+ def part_of_hash_argument?(node)
78
+ node.pair_type? || node.parent.pair_type? || (node.hash_type? && !node.braces?)
79
+ end
75
80
  end
76
81
  end
77
82
  end
@@ -34,6 +34,10 @@ module RuboCop
34
34
 
35
35
  MSG = 'Put a method call with a HEREDOC receiver on the same line as the HEREDOC opening.'
36
36
 
37
+ def self.autocorrect_incompatible_with
38
+ [Style::MethodCallWithArgsParentheses]
39
+ end
40
+
37
41
  def on_send(node)
38
42
  heredoc = heredoc_node_descendent_receiver(node)
39
43
  return unless heredoc
@@ -41,26 +41,22 @@ module RuboCop
41
41
 
42
42
  private
43
43
 
44
- # rubocop:disable-next Metrics/CyclomaticComplexity
45
44
  def check(node)
46
45
  return if node.parent&.regexp_type?
47
46
  return unless /(?<!\\)#\{.*\}/.match?(node.source)
48
47
  return if heredoc?(node)
49
- return unless node.loc.begin && node.loc.end
48
+ return unless single_quoted?(node)
50
49
  return unless valid_syntax?(node)
51
50
 
52
51
  add_offense(node) { |corrector| autocorrect(corrector, node) }
53
52
  end
54
53
 
55
- def autocorrect(corrector, node)
56
- starting_token, ending_token = if node.source.include?('"')
57
- ['%{', '}']
58
- else
59
- ['"', '"']
60
- end
54
+ def single_quoted?(node)
55
+ node.loc.begin&.is?("'") && node.loc.end
56
+ end
61
57
 
62
- corrector.replace(node.loc.begin, starting_token)
63
- corrector.replace(node.loc.end, ending_token)
58
+ def autocorrect(corrector, node)
59
+ corrector.replace(node, interpolated_source(node))
64
60
  end
65
61
 
66
62
  def heredoc?(node)
@@ -68,15 +64,22 @@ module RuboCop
68
64
  end
69
65
 
70
66
  def valid_syntax?(node)
71
- double_quoted_string = if node.source.include?('"')
72
- node.source.sub(/\A'/, '%{').sub(/'\z/, '}')
73
- else
74
- node.source.gsub(/\A'|'\z/, '"')
75
- end
76
-
77
- processed_source = parse(double_quoted_string)
67
+ processed_source = parse(interpolated_source(node))
78
68
  processed_source.valid_syntax? && processed_source.ast.dstr_type?
79
69
  end
70
+
71
+ def interpolated_source(node)
72
+ content = node.source[1...-1]
73
+ return %("#{content}") unless content.include?('"')
74
+ return "%{#{content}}" unless continued_string_part?(node)
75
+
76
+ %("#{content.gsub(/\\.|"/) { |m| m == '"' ? '\"' : m }}")
77
+ end
78
+
79
+ # A percent literal right after another string part is parsed as the `%` operator.
80
+ def continued_string_part?(node)
81
+ node.parent&.dstr_type? && node.left_sibling
82
+ end
80
83
  end
81
84
  end
82
85
  end
@@ -40,6 +40,7 @@ module RuboCop
40
40
  # NestedPercentLiteral violation to be emitted.
41
41
  PERCENT_LITERAL_TYPES = PreferredDelimiters::PERCENT_LITERAL_TYPES
42
42
  REGEXES = PERCENT_LITERAL_TYPES.map { |percent_literal| /\A#{percent_literal}\W/ }.freeze
43
+ FORMAT_SEQUENCE = /\A#{RuboCop::Cop::Utils::FormatString::SEQUENCE}\z/.freeze
43
44
 
44
45
  def on_array(node)
45
46
  process(node, *PERCENT_LITERAL_TYPES)
@@ -54,6 +55,8 @@ module RuboCop
54
55
  def contains_percent_literals?(node)
55
56
  node.each_child_node.any? do |child|
56
57
  literal = child.children.first.to_s.scrub
58
+ next false if literal.match?(FORMAT_SEQUENCE)
59
+
57
60
  REGEXES.any? { |regex| literal.match?(regex) }
58
61
  end
59
62
  end
@@ -140,6 +140,8 @@ module RuboCop
140
140
  corrected_method: correct_sym_method(to_method)
141
141
  )
142
142
  add_offense(node, message: message) do |corrector|
143
+ next if part_of_ignored_node?(node)
144
+
143
145
  remove_parentheses(corrector, node) if node.parenthesized?
144
146
 
145
147
  corrector.replace(sym_node, correct_sym_method(to_method))
@@ -57,7 +57,7 @@ module RuboCop
57
57
  node = node.children.first while node&.begin_type? && node.children.one?
58
58
  return false unless node
59
59
 
60
- keyword_operator?(node) || modifier_expression?(node)
60
+ keyword_operator?(node) || modifier_expression?(node) || command_call_operand?(node)
61
61
  end
62
62
 
63
63
  def keyword_operator?(node)
@@ -69,6 +69,27 @@ module RuboCop
69
69
  node.type?(:if, :while, :until) && node.modifier_form?
70
70
  end
71
71
 
72
+ def command_call_operand?(node)
73
+ if node.assignment? || (node.call_type? && node.assignment_method?)
74
+ command_call_value?(node.children.last)
75
+ elsif node.send_type? && node.prefix_bang?
76
+ command_call_value?(node.receiver)
77
+ else
78
+ false
79
+ end
80
+ end
81
+
82
+ def command_call_value?(node)
83
+ command_call?(node) || command_call_operand?(node)
84
+ end
85
+
86
+ def command_call?(node)
87
+ node = node.send_node if node.any_block_type?
88
+ return false unless node.type?(:call, :super, :yield)
89
+
90
+ node.arguments? && !node.parenthesized? && !node.operator_method?
91
+ end
92
+
72
93
  def chained_calls?(node)
73
94
  first_argument = node.first_argument
74
95
  first_argument.call_type? && (node.children.last&.children&.count || 0) > 1