rubocop 1.84.2 → 1.86.1

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 (187) hide show
  1. checksums.yaml +4 -4
  2. data/config/default.yml +91 -15
  3. data/config/obsoletion.yml +5 -0
  4. data/lib/rubocop/cache_config.rb +1 -1
  5. data/lib/rubocop/cli/command/auto_generate_config.rb +1 -1
  6. data/lib/rubocop/cli/command/mcp.rb +19 -0
  7. data/lib/rubocop/cli/command/show_cops.rb +2 -2
  8. data/lib/rubocop/cli/command/show_docs_url.rb +1 -1
  9. data/lib/rubocop/cli.rb +6 -3
  10. data/lib/rubocop/config.rb +14 -10
  11. data/lib/rubocop/config_finder.rb +1 -1
  12. data/lib/rubocop/config_loader_resolver.rb +2 -1
  13. data/lib/rubocop/config_obsoletion/extracted_cop.rb +4 -2
  14. data/lib/rubocop/config_store.rb +1 -1
  15. data/lib/rubocop/config_validator.rb +1 -1
  16. data/lib/rubocop/cop/correctors/condition_corrector.rb +1 -1
  17. data/lib/rubocop/cop/correctors/percent_literal_corrector.rb +2 -2
  18. data/lib/rubocop/cop/documentation.rb +2 -3
  19. data/lib/rubocop/cop/gemspec/require_mfa.rb +1 -1
  20. data/lib/rubocop/cop/internal_affairs/itblock_handler.rb +69 -0
  21. data/lib/rubocop/cop/internal_affairs.rb +1 -0
  22. data/lib/rubocop/cop/layout/argument_alignment.rb +2 -2
  23. data/lib/rubocop/cop/layout/array_alignment.rb +1 -1
  24. data/lib/rubocop/cop/layout/dot_position.rb +1 -1
  25. data/lib/rubocop/cop/layout/empty_line_after_guard_clause.rb +9 -2
  26. data/lib/rubocop/cop/layout/empty_line_between_defs.rb +1 -1
  27. data/lib/rubocop/cop/layout/empty_lines_around_attribute_accessor.rb +1 -0
  28. data/lib/rubocop/cop/layout/empty_lines_around_block_body.rb +12 -2
  29. data/lib/rubocop/cop/layout/empty_lines_around_class_body.rb +16 -2
  30. data/lib/rubocop/cop/layout/empty_lines_around_module_body.rb +16 -2
  31. data/lib/rubocop/cop/layout/end_alignment.rb +6 -3
  32. data/lib/rubocop/cop/layout/first_hash_element_indentation.rb +7 -1
  33. data/lib/rubocop/cop/layout/hash_alignment.rb +1 -1
  34. data/lib/rubocop/cop/layout/indentation_width.rb +1 -1
  35. data/lib/rubocop/cop/layout/line_length.rb +5 -3
  36. data/lib/rubocop/cop/layout/multiline_assignment_layout.rb +9 -2
  37. data/lib/rubocop/cop/layout/multiline_method_call_indentation.rb +28 -3
  38. data/lib/rubocop/cop/layout/parameter_alignment.rb +1 -1
  39. data/lib/rubocop/cop/layout/redundant_line_break.rb +1 -1
  40. data/lib/rubocop/cop/layout/space_around_block_parameters.rb +1 -1
  41. data/lib/rubocop/cop/layout/space_around_keyword.rb +3 -1
  42. data/lib/rubocop/cop/layout/space_in_lambda_literal.rb +1 -0
  43. data/lib/rubocop/cop/lint/constant_reassignment.rb +59 -9
  44. data/lib/rubocop/cop/lint/constant_resolution.rb +1 -1
  45. data/lib/rubocop/cop/lint/data_define_override.rb +63 -0
  46. data/lib/rubocop/cop/lint/duplicate_methods.rb +55 -8
  47. data/lib/rubocop/cop/lint/empty_block.rb +1 -1
  48. data/lib/rubocop/cop/lint/empty_conditional_body.rb +6 -1
  49. data/lib/rubocop/cop/lint/empty_in_pattern.rb +8 -1
  50. data/lib/rubocop/cop/lint/empty_when.rb +8 -1
  51. data/lib/rubocop/cop/lint/interpolation_check.rb +7 -2
  52. data/lib/rubocop/cop/lint/next_without_accumulator.rb +2 -0
  53. data/lib/rubocop/cop/lint/non_deterministic_require_order.rb +3 -1
  54. data/lib/rubocop/cop/lint/number_conversion.rb +1 -1
  55. data/lib/rubocop/cop/lint/redundant_cop_enable_directive.rb +0 -9
  56. data/lib/rubocop/cop/lint/redundant_safe_navigation.rb +23 -6
  57. data/lib/rubocop/cop/lint/safe_navigation_chain.rb +17 -0
  58. data/lib/rubocop/cop/lint/safe_navigation_consistency.rb +7 -1
  59. data/lib/rubocop/cop/lint/syntax.rb +25 -1
  60. data/lib/rubocop/cop/lint/trailing_comma_in_attribute_declaration.rb +1 -0
  61. data/lib/rubocop/cop/lint/unmodified_reduce_accumulator.rb +1 -0
  62. data/lib/rubocop/cop/lint/unreachable_pattern_branch.rb +113 -0
  63. data/lib/rubocop/cop/lint/unused_method_argument.rb +10 -0
  64. data/lib/rubocop/cop/lint/useless_assignment.rb +1 -1
  65. data/lib/rubocop/cop/lint/useless_constant_scoping.rb +4 -4
  66. data/lib/rubocop/cop/lint/useless_default_value_argument.rb +2 -0
  67. data/lib/rubocop/cop/lint/utils/nil_receiver_checker.rb +22 -7
  68. data/lib/rubocop/cop/lint/void.rb +32 -12
  69. data/lib/rubocop/cop/metrics/block_nesting.rb +23 -0
  70. data/lib/rubocop/cop/migration/department_name.rb +12 -1
  71. data/lib/rubocop/cop/mixin/check_line_breakable.rb +1 -1
  72. data/lib/rubocop/cop/mixin/check_single_line_suitability.rb +2 -2
  73. data/lib/rubocop/cop/mixin/hash_transform_method/autocorrection.rb +63 -0
  74. data/lib/rubocop/cop/mixin/hash_transform_method.rb +10 -60
  75. data/lib/rubocop/cop/naming/block_parameter_name.rb +1 -1
  76. data/lib/rubocop/cop/registry.rb +20 -13
  77. data/lib/rubocop/cop/security/eval.rb +15 -2
  78. data/lib/rubocop/cop/style/access_modifier_declarations.rb +14 -2
  79. data/lib/rubocop/cop/style/accessor_grouping.rb +4 -2
  80. data/lib/rubocop/cop/style/alias.rb +4 -1
  81. data/lib/rubocop/cop/style/and_or.rb +1 -0
  82. data/lib/rubocop/cop/style/arguments_forwarding.rb +25 -7
  83. data/lib/rubocop/cop/style/array_join.rb +4 -2
  84. data/lib/rubocop/cop/style/ascii_comments.rb +6 -3
  85. data/lib/rubocop/cop/style/attr.rb +5 -2
  86. data/lib/rubocop/cop/style/bare_percent_literals.rb +3 -1
  87. data/lib/rubocop/cop/style/begin_block.rb +3 -1
  88. data/lib/rubocop/cop/style/block_delimiters.rb +25 -33
  89. data/lib/rubocop/cop/style/case_equality.rb +4 -0
  90. data/lib/rubocop/cop/style/class_and_module_children.rb +10 -2
  91. data/lib/rubocop/cop/style/collection_compact.rb +36 -16
  92. data/lib/rubocop/cop/style/colon_method_call.rb +3 -1
  93. data/lib/rubocop/cop/style/concat_array_literals.rb +2 -0
  94. data/lib/rubocop/cop/style/conditional_assignment.rb +0 -4
  95. data/lib/rubocop/cop/style/copyright.rb +1 -1
  96. data/lib/rubocop/cop/style/each_for_simple_loop.rb +1 -1
  97. data/lib/rubocop/cop/style/each_with_object.rb +2 -0
  98. data/lib/rubocop/cop/style/empty_block_parameter.rb +1 -1
  99. data/lib/rubocop/cop/style/empty_class_definition.rb +43 -20
  100. data/lib/rubocop/cop/style/empty_lambda_parameter.rb +1 -1
  101. data/lib/rubocop/cop/style/encoding.rb +7 -1
  102. data/lib/rubocop/cop/style/end_block.rb +3 -1
  103. data/lib/rubocop/cop/style/endless_method.rb +8 -3
  104. data/lib/rubocop/cop/style/file_open.rb +84 -0
  105. data/lib/rubocop/cop/style/for.rb +3 -0
  106. data/lib/rubocop/cop/style/format_string_token.rb +29 -2
  107. data/lib/rubocop/cop/style/global_vars.rb +5 -2
  108. data/lib/rubocop/cop/style/guard_clause.rb +9 -6
  109. data/lib/rubocop/cop/style/hash_as_last_array_item.rb +21 -5
  110. data/lib/rubocop/cop/style/hash_lookup_method.rb +7 -0
  111. data/lib/rubocop/cop/style/hash_transform_keys.rb +17 -7
  112. data/lib/rubocop/cop/style/hash_transform_values.rb +17 -7
  113. data/lib/rubocop/cop/style/if_inside_else.rb +1 -5
  114. data/lib/rubocop/cop/style/if_unless_modifier.rb +14 -3
  115. data/lib/rubocop/cop/style/if_with_semicolon.rb +7 -5
  116. data/lib/rubocop/cop/style/inline_comment.rb +4 -1
  117. data/lib/rubocop/cop/style/ip_addresses.rb +1 -2
  118. data/lib/rubocop/cop/style/magic_comment_format.rb +2 -2
  119. data/lib/rubocop/cop/style/map_join.rb +123 -0
  120. data/lib/rubocop/cop/style/method_call_with_args_parentheses/require_parentheses.rb +5 -3
  121. data/lib/rubocop/cop/style/module_member_existence_check.rb +1 -11
  122. data/lib/rubocop/cop/style/multiline_if_then.rb +3 -1
  123. data/lib/rubocop/cop/style/mutable_constant.rb +1 -1
  124. data/lib/rubocop/cop/style/nil_comparison.rb +2 -3
  125. data/lib/rubocop/cop/style/nil_lambda.rb +1 -1
  126. data/lib/rubocop/cop/style/non_nil_check.rb +5 -11
  127. data/lib/rubocop/cop/style/not.rb +2 -0
  128. data/lib/rubocop/cop/style/numeric_literals.rb +3 -2
  129. data/lib/rubocop/cop/style/one_class_per_file.rb +115 -0
  130. data/lib/rubocop/cop/style/one_line_conditional.rb +4 -3
  131. data/lib/rubocop/cop/style/parallel_assignment.rb +4 -0
  132. data/lib/rubocop/cop/style/partition_instead_of_double_select.rb +270 -0
  133. data/lib/rubocop/cop/style/percent_literal_delimiters.rb +2 -0
  134. data/lib/rubocop/cop/style/predicate_with_kind.rb +84 -0
  135. data/lib/rubocop/cop/style/proc.rb +3 -2
  136. data/lib/rubocop/cop/style/raise_args.rb +1 -1
  137. data/lib/rubocop/cop/style/reduce_to_hash.rb +184 -0
  138. data/lib/rubocop/cop/style/redundant_begin.rb +3 -3
  139. data/lib/rubocop/cop/style/redundant_each.rb +3 -3
  140. data/lib/rubocop/cop/style/redundant_fetch_block.rb +1 -1
  141. data/lib/rubocop/cop/style/redundant_interpolation_unfreeze.rb +26 -10
  142. data/lib/rubocop/cop/style/redundant_line_continuation.rb +16 -0
  143. data/lib/rubocop/cop/style/redundant_min_max_by.rb +93 -0
  144. data/lib/rubocop/cop/style/redundant_parentheses.rb +25 -22
  145. data/lib/rubocop/cop/style/redundant_percent_q.rb +4 -1
  146. data/lib/rubocop/cop/style/redundant_return.rb +3 -1
  147. data/lib/rubocop/cop/style/redundant_self_assignment_branch.rb +0 -5
  148. data/lib/rubocop/cop/style/redundant_struct_keyword_init.rb +114 -0
  149. data/lib/rubocop/cop/style/safe_navigation.rb +7 -7
  150. data/lib/rubocop/cop/style/select_by_kind.rb +158 -0
  151. data/lib/rubocop/cop/style/select_by_range.rb +197 -0
  152. data/lib/rubocop/cop/style/select_by_regexp.rb +51 -21
  153. data/lib/rubocop/cop/style/semicolon.rb +2 -0
  154. data/lib/rubocop/cop/style/single_line_block_params.rb +2 -2
  155. data/lib/rubocop/cop/style/single_line_do_end_block.rb +1 -1
  156. data/lib/rubocop/cop/style/single_line_methods.rb +3 -1
  157. data/lib/rubocop/cop/style/special_global_vars.rb +6 -1
  158. data/lib/rubocop/cop/style/symbol_proc.rb +4 -3
  159. data/lib/rubocop/cop/style/tally_method.rb +181 -0
  160. data/lib/rubocop/cop/style/trailing_comma_in_block_args.rb +1 -1
  161. data/lib/rubocop/cop/style/trailing_method_end_statement.rb +1 -0
  162. data/lib/rubocop/cop/style/yoda_expression.rb +1 -1
  163. data/lib/rubocop/cop/variable_force/branch.rb +2 -2
  164. data/lib/rubocop/directive_comment.rb +2 -1
  165. data/lib/rubocop/formatter/disabled_config_formatter.rb +1 -1
  166. data/lib/rubocop/formatter/formatter_set.rb +1 -1
  167. data/lib/rubocop/formatter/junit_formatter.rb +1 -1
  168. data/lib/rubocop/formatter/simple_text_formatter.rb +0 -2
  169. data/lib/rubocop/formatter/worst_offenders_formatter.rb +1 -1
  170. data/lib/rubocop/formatter.rb +22 -21
  171. data/lib/rubocop/lsp/diagnostic.rb +1 -0
  172. data/lib/rubocop/lsp/routes.rb +10 -3
  173. data/lib/rubocop/mcp/server.rb +200 -0
  174. data/lib/rubocop/options.rb +10 -1
  175. data/lib/rubocop/path_util.rb +14 -2
  176. data/lib/rubocop/plugin/loader.rb +1 -1
  177. data/lib/rubocop/result_cache.rb +22 -10
  178. data/lib/rubocop/rspec/cop_helper.rb +8 -0
  179. data/lib/rubocop/rspec/shared_contexts.rb +11 -2
  180. data/lib/rubocop/runner.rb +8 -3
  181. data/lib/rubocop/server/cache.rb +5 -7
  182. data/lib/rubocop/server/core.rb +2 -0
  183. data/lib/rubocop/target_finder.rb +1 -1
  184. data/lib/rubocop/target_ruby.rb +18 -12
  185. data/lib/rubocop/version.rb +2 -2
  186. data/lib/rubocop.rb +14 -0
  187. metadata +22 -5
@@ -232,9 +232,7 @@ module RuboCop
232
232
  end
233
233
 
234
234
  def semantic_message(node)
235
- block_begin = node.loc.begin.source
236
-
237
- if block_begin == '{'
235
+ if node.braces?
238
236
  'Prefer `do...end` over `{...}` for procedural blocks.'
239
237
  else
240
238
  'Prefer `{...}` over `do...end` for functional blocks.'
@@ -372,17 +370,22 @@ module RuboCop
372
370
  end
373
371
  # rubocop:enable Metrics/CyclomaticComplexity
374
372
 
373
+ # rubocop:disable Metrics/CyclomaticComplexity -- inlined special_method checks to avoid double evaluation
375
374
  def proper_block_style?(node)
376
375
  return true if require_do_end?(node)
377
- return special_method_proper_block_style?(node) if special_method?(node.method_name)
376
+
377
+ method_name = node.method_name
378
+ return true if allowed_method?(method_name) || matches_allowed_pattern?(method_name)
379
+ return node.braces? if braces_required_method?(method_name)
378
380
 
379
381
  case style
380
382
  when :line_count_based then line_count_based_block_style?(node)
381
383
  when :semantic then semantic_block_style?(node)
382
384
  when :braces_for_chaining then braces_for_chaining_style?(node)
383
- when :always_braces then braces_style?(node)
385
+ when :always_braces then node.braces?
384
386
  end
385
387
  end
388
+ # rubocop:enable Metrics/CyclomaticComplexity
386
389
 
387
390
  def require_do_end?(node)
388
391
  return false if node.braces? || node.multiline?
@@ -391,19 +394,6 @@ module RuboCop
391
394
  resbody.children.first&.array_type?
392
395
  end
393
396
 
394
- def special_method?(method_name)
395
- allowed_method?(method_name) ||
396
- matches_allowed_pattern?(method_name) ||
397
- braces_required_method?(method_name)
398
- end
399
-
400
- def special_method_proper_block_style?(node)
401
- method_name = node.method_name
402
- return true if allowed_method?(method_name) || matches_allowed_pattern?(method_name)
403
-
404
- node.braces? if braces_required_method?(method_name)
405
- end
406
-
407
397
  def braces_required_method?(method_name)
408
398
  braces_required_methods.include?(method_name.to_s)
409
399
  end
@@ -421,24 +411,18 @@ module RuboCop
421
411
 
422
412
  if node.braces?
423
413
  functional_method?(method_name) || functional_block?(node) ||
424
- (procedural_oneliners_may_have_braces? && !node.multiline?)
414
+ (procedural_oneliners_may_have_braces? && node.single_line?)
425
415
  else
426
416
  procedural_method?(method_name) || !return_value_used?(node)
427
417
  end
428
418
  end
429
419
 
430
420
  def braces_for_chaining_style?(node)
431
- block_begin = node.loc.begin.source
432
-
433
- block_begin == if node.multiline?
434
- (node.chained? ? '{' : 'do')
435
- else
436
- '{'
437
- end
438
- end
439
-
440
- def braces_style?(node)
441
- node.loc.begin.source == '{'
421
+ if node.multiline?
422
+ node.chained? ? node.braces? : !node.braces?
423
+ else
424
+ node.braces?
425
+ end
442
426
  end
443
427
 
444
428
  def correction_would_break_code?(node)
@@ -448,7 +432,11 @@ module RuboCop
448
432
  end
449
433
 
450
434
  def functional_method?(method_name)
451
- cop_config['FunctionalMethods'].map(&:to_sym).include?(method_name)
435
+ functional_methods.include?(method_name)
436
+ end
437
+
438
+ def functional_methods
439
+ @functional_methods ||= cop_config['FunctionalMethods'].to_set(&:to_sym).freeze
452
440
  end
453
441
 
454
442
  def functional_block?(node)
@@ -460,7 +448,11 @@ module RuboCop
460
448
  end
461
449
 
462
450
  def procedural_method?(method_name)
463
- cop_config['ProceduralMethods'].map(&:to_sym).include?(method_name)
451
+ procedural_methods.include?(method_name)
452
+ end
453
+
454
+ def procedural_methods
455
+ @procedural_methods ||= cop_config['ProceduralMethods'].to_set(&:to_sym).freeze
464
456
  end
465
457
 
466
458
  def return_value_used?(node)
@@ -489,7 +481,7 @@ module RuboCop
489
481
  def begin_required?(block_node)
490
482
  # If the block contains `rescue` or `ensure`, it needs to be wrapped in
491
483
  # `begin`...`end` when changing `do-end` to `{}`.
492
- block_node.each_child_node(:rescue, :ensure).any? && !block_node.single_line?
484
+ block_node.each_child_node(:rescue, :ensure).any? && block_node.multiline?
493
485
  end
494
486
 
495
487
  def single_argument_operator_method?(node)
@@ -4,6 +4,10 @@ module RuboCop
4
4
  module Cop
5
5
  module Style
6
6
  # Checks for uses of the case equality operator (`===`).
7
+ # The `===` operator has different behavior depending on the
8
+ # receiver and its use outside of `case`/`when` is confusing.
9
+ # Prefer more explicit alternatives like `is_a?`, `include?`,
10
+ # or `match?`.
7
11
  #
8
12
  # If `AllowOnConstant` option is enabled, the cop will ignore violations when the receiver of
9
13
  # the case equality operator is a constant.
@@ -28,16 +28,24 @@ module RuboCop
28
28
  # manual oversight.
29
29
  #
30
30
  # @example EnforcedStyle: nested (default)
31
+ # # bad
32
+ # class Foo::Bar
33
+ # end
34
+ #
31
35
  # # good
32
- # # have each child on its own line
33
36
  # class Foo
34
37
  # class Bar
35
38
  # end
36
39
  # end
37
40
  #
38
41
  # @example EnforcedStyle: compact
42
+ # # bad
43
+ # class Foo
44
+ # class Bar
45
+ # end
46
+ # end
47
+ #
39
48
  # # good
40
- # # combine definitions as much as possible
41
49
  # class Foo::Bar
42
50
  # end
43
51
  #
@@ -64,22 +64,34 @@ module RuboCop
64
64
  # @!method reject_method?(node)
65
65
  def_node_matcher :reject_method?, <<~PATTERN
66
66
  (block
67
- (call
68
- !nil? {:reject :reject!})
67
+ (call !nil? {:reject :reject!})
69
68
  $(args ...)
70
- (call
71
- $(lvar _) :nil?))
69
+ (call $(lvar _) :nil?))
70
+ PATTERN
71
+
72
+ # @!method reject_method_for_numblock_or_itblock?(node)
73
+ def_node_matcher :reject_method_for_numblock_or_itblock?, <<~PATTERN
74
+ {
75
+ (numblock (call !nil? {:reject :reject!}) _ (call (lvar :_1) :nil?))
76
+ (itblock (call !nil? {:reject :reject!}) _ (call (lvar :it) :nil?))
77
+ }
72
78
  PATTERN
73
79
 
74
80
  # @!method select_method?(node)
75
81
  def_node_matcher :select_method?, <<~PATTERN
76
82
  (block
77
- (call
78
- !nil? {:select :select! :filter :filter!})
83
+ (call !nil? {:select :select! :filter :filter!})
79
84
  $(args ...)
80
85
  (call
81
- (call
82
- $(lvar _) :nil?) :!))
86
+ (call $(lvar _) :nil?) :!))
87
+ PATTERN
88
+
89
+ # @!method select_method_for_numblock_or_itblock?(node)
90
+ def_node_matcher :select_method_for_numblock_or_itblock?, <<~PATTERN
91
+ {
92
+ (numblock (call !nil? {:select :select! :filter :filter!}) _ (call (call (lvar :_1) :nil?) :!))
93
+ (itblock (call !nil? {:select :select! :filter :filter!}) _ (call (call (lvar :it) :nil?) :!))
94
+ }
83
95
  PATTERN
84
96
 
85
97
  # @!method grep_v_with_nil?(node)
@@ -102,26 +114,34 @@ module RuboCop
102
114
 
103
115
  private
104
116
 
105
- # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
106
117
  def offense_range(node)
107
118
  if reject_method_with_block_pass?(node) || grep_v_with_nil?(node)
108
119
  range(node, node)
109
120
  else
110
121
  block_node = node.parent
111
122
 
112
- return unless block_node&.block_type?
113
- unless (args, receiver = reject_method?(block_node) || select_method?(block_node))
114
- return
115
- end
116
- return unless args.last.source == receiver.source
123
+ return unless block_node&.any_block_type?
124
+ return unless match_block_method?(block_node)
117
125
 
118
126
  range(node, block_node)
119
127
  end
120
128
  end
121
- # rubocop:enable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
129
+
130
+ def match_block_method?(block_node)
131
+ if block_node.block_type?
132
+ result = reject_method?(block_node) || select_method?(block_node)
133
+ return false unless result
134
+
135
+ args, receiver = result
136
+ args.last.source == receiver.source
137
+ else
138
+ reject_method_for_numblock_or_itblock?(block_node) ||
139
+ select_method_for_numblock_or_itblock?(block_node)
140
+ end
141
+ end
122
142
 
123
143
  def to_enum_method?(node)
124
- return false unless node.receiver.send_type?
144
+ return false unless node.receiver.call_type?
125
145
 
126
146
  TO_ENUM_METHODS.include?(node.receiver.method_name)
127
147
  end
@@ -4,7 +4,9 @@ module RuboCop
4
4
  module Cop
5
5
  module Style
6
6
  # Checks for methods invoked via the `::` operator instead
7
- # of the `.` operator (like `FileUtils::rmdir` instead of `FileUtils.rmdir`).
7
+ # of the `.` operator (like `FileUtils::rmdir` instead of
8
+ # `FileUtils.rmdir`). The `::` operator is conventionally used to
9
+ # reference constants, so using it for method calls can be misleading.
8
10
  #
9
11
  # @example
10
12
  # # bad
@@ -52,6 +52,8 @@ module RuboCop
52
52
 
53
53
  add_offense(offense, message: message) do |corrector|
54
54
  if use_percent_literal
55
+ next unless prefer
56
+
55
57
  corrector.replace(offense, prefer)
56
58
  else
57
59
  corrector.replace(node.loc.selector, 'push')
@@ -99,10 +99,6 @@ module RuboCop
99
99
  end
100
100
  end
101
101
 
102
- def setter_method?(method_name)
103
- method_name.to_s.end_with?(EQUAL) && !%i[!= == === >= <=].include?(method_name)
104
- end
105
-
106
102
  def assignment_rhs_exist?(node)
107
103
  parent = node.parent
108
104
  return true unless parent
@@ -3,7 +3,7 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Style
6
- # Check that a copyright notice was given in each source file.
6
+ # Checks that a copyright notice was given in each source file.
7
7
  #
8
8
  # The default regexp for an acceptable copyright notice can be found in
9
9
  # config/default.yml. The default can be changed as follows:
@@ -26,7 +26,7 @@ module RuboCop
26
26
 
27
27
  MSG = 'Use `Integer#times` for a simple loop which iterates a fixed number of times.'
28
28
 
29
- def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler
29
+ def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler, InternalAffairs/ItblockHandler
30
30
  return unless offending?(node)
31
31
 
32
32
  send_node = node.send_node
@@ -40,6 +40,8 @@ module RuboCop
40
40
  end
41
41
  end
42
42
 
43
+ alias on_itblock on_block
44
+
43
45
  def on_numblock(node)
44
46
  each_with_object_numblock_candidate?(node) do |method, body|
45
47
  _, method_name, method_arg = *method
@@ -28,7 +28,7 @@ module RuboCop
28
28
 
29
29
  MSG = 'Omit pipes for the empty block parameters.'
30
30
 
31
- def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler
31
+ def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler, InternalAffairs/ItblockHandler
32
32
  send_node = node.send_node
33
33
  check(node) unless send_node.send_type? && send_node.lambda_literal?
34
34
  end
@@ -5,15 +5,26 @@ module RuboCop
5
5
  module Style
6
6
  # Enforces consistent style for empty class definitions.
7
7
  #
8
- # This cop can enforce either a two-line class definition or `Class.new`
8
+ # This cop can enforce either a standard class definition or `Class.new`
9
9
  # for classes with no body.
10
10
  #
11
11
  # The supported styles are:
12
12
  #
13
- # * class_definition (default) - prefer two-line class definition over `Class.new`
13
+ # * class_keyword (default) - prefer standard class definition over `Class.new`
14
14
  # * class_new - prefer `Class.new` over class definition
15
15
  #
16
- # @example EnforcedStyle: class_definition (default)
16
+ # One difference between the two styles is that the `Class.new` form does not make
17
+ # the subclass name available to the base class's `inherited` callback.
18
+ # For this reason, `EnforcedStyle: class_keyword` is set as the default style.
19
+ # Class definitions without a superclass, which are not involved in inheritance,
20
+ # are not detected. This ensures safe detection regardless of the applied style.
21
+ # This avoids overlapping responsibilities with the `Lint/EmptyClass` cop.
22
+ #
23
+ # Use `AllowedParentClasses` to permit both styles for specific parent classes.
24
+ # For example, adding `StandardError` allows both `Error = Class.new(StandardError)`
25
+ # and `class Error < StandardError; end` regardless of the enforced style.
26
+ #
27
+ # @example EnforcedStyle: class_keyword (default)
17
28
  # # bad
18
29
  # FooError = Class.new(StandardError)
19
30
  #
@@ -35,33 +46,43 @@ module RuboCop
35
46
  # # good
36
47
  # FooError = Class.new(StandardError)
37
48
  #
49
+ # @example AllowedParentClasses: ['StandardError']
50
+ # # good - allowed regardless of EnforcedStyle
51
+ # FooError = Class.new(StandardError)
52
+ #
53
+ # # good - allowed regardless of EnforcedStyle
54
+ # class FooError < StandardError
55
+ # end
56
+ #
38
57
  class EmptyClassDefinition < Base
39
58
  include ConfigurableEnforcedStyle
40
- include RangeHelp
41
59
  extend AutoCorrector
42
60
 
43
- MSG_CLASS_DEFINITION =
44
- 'Prefer a two-line class definition over `Class.new` for classes with no body.'
45
- MSG_CLASS_NEW = 'Prefer `Class.new` over class definition for classes with no body.'
61
+ MSG_CLASS_KEYWORD =
62
+ 'Use the `class` keyword instead of `Class.new` to define an empty class.'
63
+ MSG_CLASS_NEW = 'Use `Class.new` instead of the `class` keyword to define an empty class.'
46
64
 
47
65
  # @!method class_new_assignment(node)
48
66
  def_node_matcher :class_new_assignment, <<~PATTERN
49
- (casgn _ _ $(send (const _ :Class) :new ...))
67
+ (casgn _ _ $(send (const _ :Class) :new _))
50
68
  PATTERN
51
69
 
52
70
  def on_casgn(node)
53
- return unless style == :class_definition
71
+ return unless %i[class_keyword class_definition].include?(style)
54
72
  return unless (class_new_node = class_new_assignment(node))
55
73
  return if (arg = class_new_node.first_argument) && !arg.const_type?
74
+ return if allowed_parent_class?(class_new_node.first_argument.source)
56
75
 
57
- add_offense(node, message: MSG_CLASS_DEFINITION) do |corrector|
76
+ add_offense(node, message: MSG_CLASS_KEYWORD) do |corrector|
58
77
  autocorrect_class_new(corrector, node, class_new_node)
59
78
  end
60
79
  end
61
80
 
62
81
  def on_class(node)
63
82
  return unless style == :class_new
83
+ return unless node.parent_class
64
84
  return if (body = node.body) && !body.children.empty?
85
+ return if allowed_parent_class?(node.parent_class.source)
65
86
 
66
87
  add_offense(node, message: MSG_CLASS_NEW) do |corrector|
67
88
  autocorrect_class_definition(corrector, node)
@@ -70,25 +91,27 @@ module RuboCop
70
91
 
71
92
  private
72
93
 
94
+ def allowed_parent_class?(parent_class_name)
95
+ allowed_parent_classes.include?(parent_class_name)
96
+ end
97
+
98
+ def allowed_parent_classes
99
+ cop_config.fetch('AllowedParentClasses', [])
100
+ end
101
+
73
102
  def autocorrect_class_new(corrector, node, class_new_node)
74
103
  indent = ' ' * node.loc.column
75
104
  class_name = node.name
76
- if (parent_class = class_new_node.first_argument)
77
- parent_class_name = " < #{parent_class.source}"
78
- end
105
+ parent_class_name = class_new_node.first_argument.source
79
106
 
80
- corrector.replace(node, "class #{class_name}#{parent_class_name}\n#{indent}end")
107
+ corrector.replace(node, "class #{class_name} < #{parent_class_name}\n#{indent}end")
81
108
  end
82
109
 
83
110
  def autocorrect_class_definition(corrector, node)
84
- indent = ' ' * node.loc.column
85
111
  class_name = node.identifier.source
86
- if (parent_class = node.parent_class)
87
- parent_class_name = "(#{parent_class.source})"
88
- end
89
- range = range_by_whole_lines(node.source_range, include_final_newline: true)
112
+ parent_class_name = node.parent_class.source
90
113
 
91
- corrector.replace(range, "#{indent}#{class_name} = Class.new#{parent_class_name}\n")
114
+ corrector.replace(node, "#{class_name} = Class.new(#{parent_class_name})")
92
115
  end
93
116
  end
94
117
  end
@@ -23,7 +23,7 @@ module RuboCop
23
23
 
24
24
  MSG = 'Omit parentheses for the empty lambda parameters.'
25
25
 
26
- def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler
26
+ def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler, InternalAffairs/ItblockHandler
27
27
  send_node = node.send_node
28
28
  return unless send_node.send_type?
29
29
 
@@ -3,12 +3,18 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Style
6
- # Checks ensures source files have no utf-8 encoding comments.
6
+ # Checks that source files have no utf-8 encoding comments.
7
+ # Since Ruby 2.0, UTF-8 is the default source encoding, so
8
+ # these comments are no longer necessary and just add noise.
9
+ #
7
10
  # @example
8
11
  # # bad
9
12
  # # encoding: UTF-8
10
13
  # # coding: UTF-8
11
14
  # # -*- coding: UTF-8 -*-
15
+ #
16
+ # # good
17
+ # # No encoding comment needed
12
18
  class Encoding < Base
13
19
  include RangeHelp
14
20
  extend AutoCorrector
@@ -3,7 +3,9 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Style
6
- # Checks for END blocks.
6
+ # Checks for `END` blocks. `END` blocks are Perl-style constructs
7
+ # and `Kernel#at_exit` is the idiomatic Ruby alternative, as it's
8
+ # explicit and can be used anywhere.
7
9
  #
8
10
  # @example
9
11
  # # bad
@@ -157,6 +157,7 @@ module RuboCop
157
157
  handle_require_always_style(node)
158
158
  end
159
159
  end
160
+ alias on_defs on_def
160
161
 
161
162
  private
162
163
 
@@ -170,7 +171,7 @@ module RuboCop
170
171
  end
171
172
 
172
173
  def handle_require_single_line_style(node)
173
- if node.endless? && !node.single_line?
174
+ if node.endless? && node.multiline?
174
175
  add_offense(node, message: MSG_MULTI_LINE) do |corrector|
175
176
  correct_to_multiline(corrector, node)
176
177
  end
@@ -207,7 +208,7 @@ module RuboCop
207
208
 
208
209
  def correct_to_multiline(corrector, node)
209
210
  replacement = <<~RUBY.strip
210
- def #{node.method_name}#{arguments(node)}
211
+ def #{receiver(node)}#{node.method_name}#{arguments(node)}
211
212
  #{node.body.source}
212
213
  end
213
214
  RUBY
@@ -217,10 +218,14 @@ module RuboCop
217
218
 
218
219
  def endless_replacement(node)
219
220
  <<~RUBY.strip
220
- def #{node.method_name}#{arguments(node)} = #{node.body.source}
221
+ def #{receiver(node)}#{node.method_name}#{arguments(node)} = #{node.body.source}
221
222
  RUBY
222
223
  end
223
224
 
225
+ def receiver(node)
226
+ node.receiver ? "#{node.receiver.source}#{node.loc.operator.source}" : ''
227
+ end
228
+
224
229
  def arguments(node, missing = '')
225
230
  node.arguments.any? ? node.arguments.source : missing
226
231
  end
@@ -0,0 +1,84 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Style
6
+ # Checks for `File.open` without a block, which can leak file descriptors.
7
+ #
8
+ # When `File.open` is called without a block, the caller is responsible
9
+ # for closing the file descriptor. If it is not explicitly closed, it
10
+ # will only be closed when the garbage collector runs, which may lead
11
+ # to resource exhaustion. Using the block form ensures the file is
12
+ # automatically closed when the block exits.
13
+ #
14
+ # This cop only registers an offense when the result of `File.open` is
15
+ # assigned to a variable or has a method chained on it, as those are the
16
+ # clearest indicators that the block form should be used instead. When
17
+ # `File.open` is used as a return value or passed as an argument, the
18
+ # caller is likely managing the file descriptor intentionally.
19
+ #
20
+ # @safety
21
+ # This cop is unsafe because it relies on syntax heuristics and cannot
22
+ # verify whether the file descriptor is safely managed. For example, it
23
+ # still flags intentional one-shot reads (`File.open("f").read`) where
24
+ # the file descriptor is closed by the garbage collector.
25
+ #
26
+ # @example
27
+ # # bad
28
+ # f = File.open('file')
29
+ #
30
+ # # bad
31
+ # File.open('file').read
32
+ #
33
+ # # good
34
+ # File.open('file') do |f|
35
+ # f.read
36
+ # end
37
+ #
38
+ # # good
39
+ # File.open('file', &:read)
40
+ #
41
+ # # good - pass an open file object to an API that manages its lifecycle
42
+ # process(io: File.open('file'))
43
+ #
44
+ # # good - return an open file object for the caller to manage
45
+ # def json_key_io
46
+ # File.open('file')
47
+ # end
48
+ #
49
+ # # good - use File.read for one-shot reads
50
+ # File.read('file')
51
+ #
52
+ class FileOpen < Base
53
+ MSG = '`File.open` without a block may leak a file descriptor; use the block form.'
54
+ RESTRICT_ON_SEND = %i[open].freeze
55
+
56
+ # @!method file_open?(node)
57
+ def_node_matcher :file_open?, <<~PATTERN
58
+ (send (const {nil? cbase} :File) :open ...)
59
+ PATTERN
60
+
61
+ def on_send(node)
62
+ return unless file_open?(node)
63
+ return if node.block_argument?
64
+ return unless offensive_usage?(node)
65
+
66
+ add_offense(node)
67
+ end
68
+ alias on_csend on_send
69
+
70
+ private
71
+
72
+ def offensive_usage?(node)
73
+ return true unless node.value_used?
74
+
75
+ node.parent.lvasgn_type? || receiver_of_chained_call?(node)
76
+ end
77
+
78
+ def receiver_of_chained_call?(node)
79
+ node.parent.receiver == node
80
+ end
81
+ end
82
+ end
83
+ end
84
+ end
@@ -8,6 +8,9 @@ module RuboCop
8
8
  # parameter. An `each` call with a block on a single line is always
9
9
  # allowed.
10
10
  #
11
+ # NOTE: `each` is preferred in idiomatic Ruby because `for` leaks
12
+ # its loop variable into the surrounding scope.
13
+ #
11
14
  # @example EnforcedStyle: each (default)
12
15
  # # bad
13
16
  # def foo
@@ -18,6 +18,11 @@ module RuboCop
18
18
  # of `EnforcedStyle`) are only considered if used in the format string argument to the
19
19
  # methods `printf`, `sprintf`, `format` and `%`.
20
20
  #
21
+ # NOTE: In `aggressive` mode, offenses are registered for all strings containing tokens,
22
+ # but autocorrection is only applied when the string appears in a known formatting context
23
+ # (`format`, `sprintf`, `printf`, or `%`). This is done in order to prevent false
24
+ # autocorrections for strings that are not actually format strings.
25
+ #
21
26
  # NOTE: Tokens in the `unannotated` style (eg. `%s`) are always treated as if
22
27
  # configured with `Conservative: true`. This is done in order to prevent false positives,
23
28
  # because this format is very similar to encoded URLs or Date/Time formatting strings.
@@ -90,9 +95,25 @@ module RuboCop
90
95
  # # good
91
96
  # redirect('foo/%{bar_id}')
92
97
  #
98
+ # @example Mode: aggressive (default), EnforcedStyle: annotated
99
+ #
100
+ # # bad
101
+ # "%{greeting}"
102
+ # foo("%{greeting}")
103
+ #
104
+ # # bad
105
+ # format("%{greeting}", greeting: 'Hello')
106
+ # printf("%{greeting}", greeting: 'Hello')
107
+ # sprintf("%{greeting}", greeting: 'Hello')
108
+ # "%{greeting}" % { greeting: 'Hello' }
109
+ #
110
+ # # good
111
+ # format("%<greeting>s", greeting: 'Hello')
112
+ # printf("%<greeting>s", greeting: 'Hello')
113
+ # sprintf("%<greeting>s", greeting: 'Hello')
114
+ # "%<greeting>s" % { greeting: 'Hello' }
115
+ #
93
116
  # @example Mode: conservative, EnforcedStyle: annotated
94
- # # In `conservative` mode, offenses are only registered for strings
95
- # # given to a known formatting method.
96
117
  #
97
118
  # # good
98
119
  # "%{greeting}"
@@ -104,6 +125,12 @@ module RuboCop
104
125
  # sprintf("%{greeting}", greeting: 'Hello')
105
126
  # "%{greeting}" % { greeting: 'Hello' }
106
127
  #
128
+ # # good
129
+ # format("%<greeting>s", greeting: 'Hello')
130
+ # printf("%<greeting>s", greeting: 'Hello')
131
+ # sprintf("%<greeting>s", greeting: 'Hello')
132
+ # "%<greeting>s" % { greeting: 'Hello' }
133
+ #
107
134
  class FormatStringToken < Base
108
135
  include ConfigurableEnforcedStyle
109
136
  include AllowedMethods