rubocop 1.84.2 → 1.86.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (176) hide show
  1. checksums.yaml +4 -4
  2. data/config/default.yml +86 -14
  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 +1 -1
  11. data/lib/rubocop/config_finder.rb +1 -1
  12. data/lib/rubocop/config_obsoletion/extracted_cop.rb +4 -2
  13. data/lib/rubocop/config_store.rb +1 -1
  14. data/lib/rubocop/config_validator.rb +1 -1
  15. data/lib/rubocop/cop/correctors/condition_corrector.rb +1 -1
  16. data/lib/rubocop/cop/correctors/percent_literal_corrector.rb +2 -2
  17. data/lib/rubocop/cop/gemspec/require_mfa.rb +1 -1
  18. data/lib/rubocop/cop/internal_affairs/itblock_handler.rb +69 -0
  19. data/lib/rubocop/cop/internal_affairs.rb +1 -0
  20. data/lib/rubocop/cop/layout/argument_alignment.rb +2 -2
  21. data/lib/rubocop/cop/layout/array_alignment.rb +1 -1
  22. data/lib/rubocop/cop/layout/dot_position.rb +1 -1
  23. data/lib/rubocop/cop/layout/empty_line_after_guard_clause.rb +9 -2
  24. data/lib/rubocop/cop/layout/empty_line_between_defs.rb +1 -1
  25. data/lib/rubocop/cop/layout/empty_lines_around_attribute_accessor.rb +1 -0
  26. data/lib/rubocop/cop/layout/empty_lines_around_block_body.rb +12 -2
  27. data/lib/rubocop/cop/layout/empty_lines_around_class_body.rb +16 -2
  28. data/lib/rubocop/cop/layout/empty_lines_around_module_body.rb +16 -2
  29. data/lib/rubocop/cop/layout/end_alignment.rb +3 -2
  30. data/lib/rubocop/cop/layout/first_hash_element_indentation.rb +7 -1
  31. data/lib/rubocop/cop/layout/hash_alignment.rb +1 -1
  32. data/lib/rubocop/cop/layout/indentation_width.rb +1 -1
  33. data/lib/rubocop/cop/layout/multiline_assignment_layout.rb +9 -2
  34. data/lib/rubocop/cop/layout/multiline_method_call_indentation.rb +28 -3
  35. data/lib/rubocop/cop/layout/parameter_alignment.rb +1 -1
  36. data/lib/rubocop/cop/layout/redundant_line_break.rb +1 -1
  37. data/lib/rubocop/cop/layout/space_around_block_parameters.rb +1 -1
  38. data/lib/rubocop/cop/layout/space_around_keyword.rb +3 -1
  39. data/lib/rubocop/cop/layout/space_in_lambda_literal.rb +1 -0
  40. data/lib/rubocop/cop/lint/constant_reassignment.rb +59 -9
  41. data/lib/rubocop/cop/lint/constant_resolution.rb +1 -1
  42. data/lib/rubocop/cop/lint/data_define_override.rb +63 -0
  43. data/lib/rubocop/cop/lint/duplicate_methods.rb +50 -8
  44. data/lib/rubocop/cop/lint/empty_block.rb +1 -1
  45. data/lib/rubocop/cop/lint/empty_conditional_body.rb +6 -1
  46. data/lib/rubocop/cop/lint/empty_in_pattern.rb +8 -1
  47. data/lib/rubocop/cop/lint/empty_when.rb +8 -1
  48. data/lib/rubocop/cop/lint/interpolation_check.rb +7 -2
  49. data/lib/rubocop/cop/lint/next_without_accumulator.rb +2 -0
  50. data/lib/rubocop/cop/lint/non_deterministic_require_order.rb +3 -1
  51. data/lib/rubocop/cop/lint/number_conversion.rb +1 -1
  52. data/lib/rubocop/cop/lint/redundant_cop_enable_directive.rb +0 -9
  53. data/lib/rubocop/cop/lint/redundant_safe_navigation.rb +23 -6
  54. data/lib/rubocop/cop/lint/safe_navigation_chain.rb +17 -0
  55. data/lib/rubocop/cop/lint/safe_navigation_consistency.rb +7 -1
  56. data/lib/rubocop/cop/lint/trailing_comma_in_attribute_declaration.rb +1 -0
  57. data/lib/rubocop/cop/lint/unmodified_reduce_accumulator.rb +1 -0
  58. data/lib/rubocop/cop/lint/unreachable_pattern_branch.rb +113 -0
  59. data/lib/rubocop/cop/lint/useless_assignment.rb +1 -1
  60. data/lib/rubocop/cop/lint/useless_constant_scoping.rb +4 -4
  61. data/lib/rubocop/cop/lint/useless_default_value_argument.rb +2 -0
  62. data/lib/rubocop/cop/lint/utils/nil_receiver_checker.rb +6 -5
  63. data/lib/rubocop/cop/lint/void.rb +32 -12
  64. data/lib/rubocop/cop/metrics/block_nesting.rb +23 -0
  65. data/lib/rubocop/cop/migration/department_name.rb +12 -1
  66. data/lib/rubocop/cop/mixin/check_line_breakable.rb +1 -1
  67. data/lib/rubocop/cop/mixin/check_single_line_suitability.rb +2 -2
  68. data/lib/rubocop/cop/mixin/hash_transform_method/autocorrection.rb +63 -0
  69. data/lib/rubocop/cop/mixin/hash_transform_method.rb +10 -60
  70. data/lib/rubocop/cop/naming/block_parameter_name.rb +1 -1
  71. data/lib/rubocop/cop/registry.rb +20 -13
  72. data/lib/rubocop/cop/security/eval.rb +15 -2
  73. data/lib/rubocop/cop/style/accessor_grouping.rb +4 -2
  74. data/lib/rubocop/cop/style/alias.rb +4 -1
  75. data/lib/rubocop/cop/style/and_or.rb +1 -0
  76. data/lib/rubocop/cop/style/arguments_forwarding.rb +25 -7
  77. data/lib/rubocop/cop/style/array_join.rb +4 -2
  78. data/lib/rubocop/cop/style/ascii_comments.rb +6 -3
  79. data/lib/rubocop/cop/style/attr.rb +5 -2
  80. data/lib/rubocop/cop/style/bare_percent_literals.rb +3 -1
  81. data/lib/rubocop/cop/style/begin_block.rb +3 -1
  82. data/lib/rubocop/cop/style/block_delimiters.rb +25 -33
  83. data/lib/rubocop/cop/style/case_equality.rb +4 -0
  84. data/lib/rubocop/cop/style/class_and_module_children.rb +10 -2
  85. data/lib/rubocop/cop/style/collection_compact.rb +36 -16
  86. data/lib/rubocop/cop/style/colon_method_call.rb +3 -1
  87. data/lib/rubocop/cop/style/concat_array_literals.rb +2 -0
  88. data/lib/rubocop/cop/style/conditional_assignment.rb +0 -4
  89. data/lib/rubocop/cop/style/copyright.rb +1 -1
  90. data/lib/rubocop/cop/style/each_for_simple_loop.rb +1 -1
  91. data/lib/rubocop/cop/style/each_with_object.rb +2 -0
  92. data/lib/rubocop/cop/style/empty_block_parameter.rb +1 -1
  93. data/lib/rubocop/cop/style/empty_class_definition.rb +43 -20
  94. data/lib/rubocop/cop/style/empty_lambda_parameter.rb +1 -1
  95. data/lib/rubocop/cop/style/encoding.rb +7 -1
  96. data/lib/rubocop/cop/style/end_block.rb +3 -1
  97. data/lib/rubocop/cop/style/endless_method.rb +8 -3
  98. data/lib/rubocop/cop/style/file_open.rb +84 -0
  99. data/lib/rubocop/cop/style/for.rb +3 -0
  100. data/lib/rubocop/cop/style/format_string_token.rb +29 -2
  101. data/lib/rubocop/cop/style/global_vars.rb +5 -2
  102. data/lib/rubocop/cop/style/hash_as_last_array_item.rb +21 -5
  103. data/lib/rubocop/cop/style/hash_lookup_method.rb +7 -0
  104. data/lib/rubocop/cop/style/hash_transform_keys.rb +17 -7
  105. data/lib/rubocop/cop/style/hash_transform_values.rb +17 -7
  106. data/lib/rubocop/cop/style/if_unless_modifier.rb +14 -3
  107. data/lib/rubocop/cop/style/if_with_semicolon.rb +7 -5
  108. data/lib/rubocop/cop/style/inline_comment.rb +4 -1
  109. data/lib/rubocop/cop/style/ip_addresses.rb +1 -2
  110. data/lib/rubocop/cop/style/magic_comment_format.rb +2 -2
  111. data/lib/rubocop/cop/style/map_join.rb +123 -0
  112. data/lib/rubocop/cop/style/method_call_with_args_parentheses/require_parentheses.rb +5 -3
  113. data/lib/rubocop/cop/style/multiline_if_then.rb +3 -1
  114. data/lib/rubocop/cop/style/mutable_constant.rb +1 -1
  115. data/lib/rubocop/cop/style/nil_comparison.rb +2 -3
  116. data/lib/rubocop/cop/style/nil_lambda.rb +1 -1
  117. data/lib/rubocop/cop/style/non_nil_check.rb +5 -11
  118. data/lib/rubocop/cop/style/not.rb +2 -0
  119. data/lib/rubocop/cop/style/numeric_literals.rb +3 -2
  120. data/lib/rubocop/cop/style/one_class_per_file.rb +95 -0
  121. data/lib/rubocop/cop/style/one_line_conditional.rb +4 -3
  122. data/lib/rubocop/cop/style/parallel_assignment.rb +4 -0
  123. data/lib/rubocop/cop/style/partition_instead_of_double_select.rb +270 -0
  124. data/lib/rubocop/cop/style/percent_literal_delimiters.rb +2 -0
  125. data/lib/rubocop/cop/style/predicate_with_kind.rb +84 -0
  126. data/lib/rubocop/cop/style/proc.rb +3 -2
  127. data/lib/rubocop/cop/style/raise_args.rb +1 -1
  128. data/lib/rubocop/cop/style/reduce_to_hash.rb +184 -0
  129. data/lib/rubocop/cop/style/redundant_begin.rb +3 -3
  130. data/lib/rubocop/cop/style/redundant_each.rb +3 -3
  131. data/lib/rubocop/cop/style/redundant_fetch_block.rb +1 -1
  132. data/lib/rubocop/cop/style/redundant_interpolation_unfreeze.rb +26 -10
  133. data/lib/rubocop/cop/style/redundant_min_max_by.rb +93 -0
  134. data/lib/rubocop/cop/style/redundant_parentheses.rb +22 -22
  135. data/lib/rubocop/cop/style/redundant_percent_q.rb +4 -1
  136. data/lib/rubocop/cop/style/redundant_return.rb +3 -1
  137. data/lib/rubocop/cop/style/redundant_self_assignment_branch.rb +0 -5
  138. data/lib/rubocop/cop/style/redundant_struct_keyword_init.rb +104 -0
  139. data/lib/rubocop/cop/style/safe_navigation.rb +7 -7
  140. data/lib/rubocop/cop/style/select_by_kind.rb +158 -0
  141. data/lib/rubocop/cop/style/select_by_range.rb +197 -0
  142. data/lib/rubocop/cop/style/select_by_regexp.rb +51 -21
  143. data/lib/rubocop/cop/style/semicolon.rb +2 -0
  144. data/lib/rubocop/cop/style/single_line_block_params.rb +2 -2
  145. data/lib/rubocop/cop/style/single_line_do_end_block.rb +1 -1
  146. data/lib/rubocop/cop/style/single_line_methods.rb +3 -1
  147. data/lib/rubocop/cop/style/special_global_vars.rb +6 -1
  148. data/lib/rubocop/cop/style/symbol_proc.rb +4 -3
  149. data/lib/rubocop/cop/style/tally_method.rb +181 -0
  150. data/lib/rubocop/cop/style/trailing_comma_in_block_args.rb +1 -1
  151. data/lib/rubocop/cop/style/trailing_method_end_statement.rb +1 -0
  152. data/lib/rubocop/cop/style/yoda_expression.rb +1 -1
  153. data/lib/rubocop/cop/variable_force/branch.rb +2 -2
  154. data/lib/rubocop/directive_comment.rb +2 -1
  155. data/lib/rubocop/formatter/disabled_config_formatter.rb +1 -1
  156. data/lib/rubocop/formatter/formatter_set.rb +1 -1
  157. data/lib/rubocop/formatter/junit_formatter.rb +1 -1
  158. data/lib/rubocop/formatter/simple_text_formatter.rb +0 -2
  159. data/lib/rubocop/formatter/worst_offenders_formatter.rb +1 -1
  160. data/lib/rubocop/formatter.rb +22 -21
  161. data/lib/rubocop/lsp/diagnostic.rb +1 -0
  162. data/lib/rubocop/lsp/routes.rb +10 -3
  163. data/lib/rubocop/mcp/server.rb +198 -0
  164. data/lib/rubocop/options.rb +10 -1
  165. data/lib/rubocop/path_util.rb +14 -2
  166. data/lib/rubocop/plugin/loader.rb +1 -1
  167. data/lib/rubocop/result_cache.rb +22 -10
  168. data/lib/rubocop/rspec/shared_contexts.rb +11 -2
  169. data/lib/rubocop/runner.rb +8 -3
  170. data/lib/rubocop/server/cache.rb +5 -7
  171. data/lib/rubocop/server/core.rb +2 -0
  172. data/lib/rubocop/target_finder.rb +1 -1
  173. data/lib/rubocop/target_ruby.rb +18 -12
  174. data/lib/rubocop/version.rb +2 -2
  175. data/lib/rubocop.rb +14 -0
  176. metadata +20 -3
@@ -0,0 +1,84 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Style
6
+ # Looks for uses of `any?`, `all?`, `none?`, or `one?` with a block
7
+ # containing only an `is_a?`, `kind_of?`, or `instance_of?` check, and
8
+ # suggests using the predicate method with the class argument directly.
9
+ #
10
+ # @safety
11
+ # This cop is unsafe because `instance_of?` checks for an exact class
12
+ # match, while the pattern argument uses `===` which also matches
13
+ # subclasses. For `is_a?` and `kind_of?`, the behavior is equivalent.
14
+ #
15
+ # @example
16
+ # # bad
17
+ # array.any? { |x| x.is_a?(Integer) }
18
+ # array.all? { |x| x.kind_of?(String) }
19
+ # array.none? { |x| x.is_a?(Float) }
20
+ # array.one? { |x| x.instance_of?(Symbol) }
21
+ #
22
+ # # good
23
+ # array.any?(Integer)
24
+ # array.all?(String)
25
+ # array.none?(Float)
26
+ # array.one?(Symbol)
27
+ class PredicateWithKind < Base
28
+ extend AutoCorrector
29
+ include RangeHelp
30
+
31
+ MSG = 'Prefer `%<replacement>s` to `%<original>s` with a kind check.'
32
+ RESTRICT_ON_SEND = %i[any? all? none? one?].freeze
33
+ KIND_METHODS = %i[is_a? kind_of? instance_of?].to_set.freeze
34
+
35
+ # @!method kind_check?(node)
36
+ def_node_matcher :kind_check?, <<~PATTERN
37
+ {
38
+ (block call (args (arg $_)) $(send (lvar _) %KIND_METHODS _))
39
+ (numblock call $1 $(send (lvar _) %KIND_METHODS _))
40
+ (itblock call $_ $(send (lvar _) %KIND_METHODS _))
41
+ }
42
+ PATTERN
43
+
44
+ # @!method kind_call?(node, name)
45
+ def_node_matcher :kind_call?, <<~PATTERN
46
+ (send (lvar %1) %KIND_METHODS _)
47
+ PATTERN
48
+
49
+ def on_send(node)
50
+ return unless (block_node = node.block_node)
51
+ return if block_node.body&.begin_type?
52
+ return unless (kind_check_node = extract_send_node(block_node))
53
+
54
+ klass = kind_check_node.first_argument
55
+ replacement = "#{node.method_name}(#{klass.source})"
56
+
57
+ register_offense(node, block_node, klass, replacement)
58
+ end
59
+ alias on_csend on_send
60
+
61
+ private
62
+
63
+ def extract_send_node(block_node)
64
+ return unless (block_arg_name, kind_check_node = kind_check?(block_node))
65
+
66
+ block_arg_name = :"_#{block_arg_name}" if block_node.numblock_type?
67
+ block_arg_name = :it if block_node.type?(:itblock)
68
+
69
+ kind_check_node if kind_call?(kind_check_node, block_arg_name)
70
+ end
71
+
72
+ def register_offense(node, block_node, klass, replacement)
73
+ original = "#{node.method_name} { ... }"
74
+ message = format(MSG, replacement: replacement, original: original)
75
+
76
+ add_offense(block_node, message: message) do |corrector|
77
+ range = range_between(node.loc.selector.begin_pos, block_node.loc.end.end_pos)
78
+ corrector.replace(range, "#{node.method_name}(#{klass.source})")
79
+ end
80
+ end
81
+ end
82
+ end
83
+ end
84
+ end
@@ -3,8 +3,9 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Style
6
- # Checks for uses of Proc.new where Kernel#proc
7
- # would be more appropriate.
6
+ # Checks for uses of `Proc.new` where `Kernel#proc`
7
+ # would be more appropriate. `proc` is the shorter and
8
+ # more idiomatic way to create procs in Ruby.
8
9
  #
9
10
  # @example
10
11
  # # bad
@@ -51,7 +51,7 @@ module RuboCop
51
51
  EXPLODED_MSG = 'Provide an exception class and message as arguments to `%<method>s`.'
52
52
  COMPACT_MSG = 'Provide an exception object as an argument to `%<method>s`.'
53
53
  ACCEPTABLE_ARG_TYPES = %i[
54
- hash forwarded_restarg splat forwarded_restarg forwarded_args
54
+ hash forwarded_restarg splat forwarded_kwrestarg forwarded_args
55
55
  ].freeze
56
56
 
57
57
  RESTRICT_ON_SEND = %i[raise fail].freeze
@@ -0,0 +1,184 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Style
6
+ # Checks for `each_with_object`, `inject`, and `reduce` calls that build
7
+ # a hash from an enumerable, where `to_h` with a block could be used instead.
8
+ #
9
+ # This cop complements `Style/HashTransformKeys` and `Style/HashTransformValues`,
10
+ # which handle hash-to-hash transformations with destructured key-value pairs.
11
+ # This cop targets the case where a hash is built from individual elements
12
+ # (non-destructured block parameter).
13
+ #
14
+ # @safety
15
+ # This cop is unsafe because it cannot guarantee that the receiver
16
+ # is an `Enumerable` by static analysis, so the correction may
17
+ # not be actually equivalent. Additionally, `each_with_object` returns
18
+ # the hash object while `to_h` returns a new hash, which could matter
19
+ # if the hash object identity is important.
20
+ #
21
+ # @example
22
+ # # bad
23
+ # array.each_with_object({}) { |elem, hash| hash[elem.id] = elem.name }
24
+ #
25
+ # # bad
26
+ # array.inject({}) { |hash, elem| hash[elem.id] = elem.name; hash }
27
+ #
28
+ # # bad
29
+ # array.reduce({}) { |hash, elem| hash[elem.id] = elem.name; hash }
30
+ #
31
+ # # bad
32
+ # array.each_with_object({}) { |elem, hash| hash[elem] = elem.to_s }
33
+ #
34
+ # # good
35
+ # array.to_h { |elem| [elem.id, elem.name] }
36
+ #
37
+ # # good
38
+ # array.to_h { |elem| [elem, elem.to_s] }
39
+ #
40
+ class ReduceToHash < Base
41
+ extend AutoCorrector
42
+ extend TargetRubyVersion
43
+ include RangeHelp
44
+
45
+ minimum_target_ruby_version 2.6
46
+
47
+ MSG = 'Use `to_h { ... }` instead of `%<method>s`.'
48
+ RESTRICT_ON_SEND = %i[each_with_object inject reduce].freeze
49
+
50
+ # each_with_object({}) { |elem, hash| hash[key] = value }
51
+ # @!method each_with_object_to_hash?(node)
52
+ def_node_matcher :each_with_object_to_hash?, <<~PATTERN
53
+ {
54
+ (block
55
+ (call _ :each_with_object (hash))
56
+ (args (arg _elem) (arg _hash))
57
+ (send (lvar _hash) :[]= $_key $_value))
58
+ (numblock
59
+ (call _ :each_with_object (hash))
60
+ 2
61
+ (send (lvar :_2) :[]= $_key $_value))
62
+ }
63
+ PATTERN
64
+
65
+ # inject/reduce({}) { |hash, elem| hash[key] = value; hash }
66
+ # @!method inject_to_hash?(node)
67
+ def_node_matcher :inject_to_hash?, <<~PATTERN
68
+ {
69
+ (block
70
+ (call _ {:inject :reduce} (hash))
71
+ (args (arg _hash) (arg _elem))
72
+ (begin
73
+ (send (lvar _hash) :[]= $_key $_value)
74
+ (lvar _hash)))
75
+ (numblock
76
+ (call _ {:inject :reduce} (hash))
77
+ 2
78
+ (begin
79
+ (send (lvar :_1) :[]= $_key $_value)
80
+ (lvar :_1)))
81
+ }
82
+ PATTERN
83
+
84
+ def on_send(node)
85
+ block_node = node.block_node
86
+ return unless block_node
87
+
88
+ check_offense(node, block_node)
89
+ end
90
+ alias on_csend on_send
91
+
92
+ private
93
+
94
+ def check_offense(node, block_node)
95
+ key, value = if node.method?(:each_with_object)
96
+ each_with_object_to_hash?(block_node)
97
+ else
98
+ inject_to_hash?(block_node)
99
+ end
100
+ return unless key
101
+ return if accumulator_used_in_expressions?(block_node, key, value)
102
+
103
+ register_offense(node, block_node, key, value)
104
+ end
105
+
106
+ def accumulator_used_in_expressions?(block_node, key, value)
107
+ acc_name = accumulator_name(block_node)
108
+ references_variable?(key, acc_name) || references_variable?(value, acc_name)
109
+ end
110
+
111
+ def accumulator_name(block_node)
112
+ index = block_node.method?(:each_with_object) ? 1 : 0
113
+ block_node.argument_list[index].name
114
+ end
115
+
116
+ def references_variable?(node, name)
117
+ node.each_node(:lvar).any? { |lvar| lvar.children.first == name }
118
+ end
119
+
120
+ def register_offense(send_node, block_node, key_expr, value_expr)
121
+ message = format(MSG, method: send_node.method_name)
122
+
123
+ add_offense(send_node.loc.selector, message: message) do |corrector|
124
+ corrector.replace(
125
+ replacement_range(send_node, block_node),
126
+ replacement(block_node, key_expr, value_expr)
127
+ )
128
+ end
129
+ end
130
+
131
+ def replacement(block_node, key_expr, value_expr)
132
+ key_source = adjusted_source(key_expr, block_node)
133
+ value_source = adjusted_source(value_expr, block_node)
134
+ body = "[#{key_source}, #{value_source}]"
135
+
136
+ if block_node.numblock_type?
137
+ block_node.braces? ? "to_h { #{body} }" : do_end_replacement(block_node, body)
138
+ else
139
+ named_block_replacement(block_node, body)
140
+ end
141
+ end
142
+
143
+ def named_block_replacement(block_node, body)
144
+ arg = element_arg_source(block_node)
145
+ if block_node.braces?
146
+ "to_h { |#{arg}| #{body} }"
147
+ else
148
+ do_end_replacement(block_node, body, arg)
149
+ end
150
+ end
151
+
152
+ def do_end_replacement(block_node, body, arg = nil)
153
+ args = arg ? " |#{arg}|" : ''
154
+ "to_h do#{args}\n#{indent(block_node)} #{body}\n#{indent(block_node)}end"
155
+ end
156
+
157
+ def replacement_range(send_node, block_node)
158
+ range_between(send_node.loc.selector.begin_pos, block_node.source_range.end_pos)
159
+ end
160
+
161
+ def element_arg_source(block_node)
162
+ if block_node.method?(:each_with_object)
163
+ block_node.first_argument.source
164
+ else
165
+ block_node.arguments[1].source
166
+ end
167
+ end
168
+
169
+ def adjusted_source(expr_node, block_node)
170
+ source = expr_node.source
171
+ return source unless block_node.numblock_type?
172
+ return source if block_node.method?(:each_with_object)
173
+
174
+ # For inject/reduce numblocks, _2 is the element (becomes _1)
175
+ source.gsub('_2', '_1')
176
+ end
177
+
178
+ def indent(node)
179
+ ' ' * node.source_range.column
180
+ end
181
+ end
182
+ end
183
+ end
184
+ end
@@ -3,9 +3,9 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Style
6
- # Checks for redundant `begin` blocks.
7
- #
8
- # Currently it checks for code like this:
6
+ # Checks for redundant `begin` blocks. A `begin` block is redundant
7
+ # when the `rescue`/`ensure` can be handled by the enclosing method
8
+ # or block definition directly, avoiding unnecessary indentation.
9
9
  #
10
10
  # @example
11
11
  #
@@ -64,7 +64,7 @@ module RuboCop
64
64
  def redundant_each_method(node)
65
65
  return if node.last_argument&.block_pass_type?
66
66
 
67
- if node.method?(:each) && !node.parent&.block_type?
67
+ if node.method?(:each) && !node.parent&.any_block_type?
68
68
  ancestor_node = node.each_ancestor(:call).detect do |ancestor|
69
69
  ancestor.receiver == node &&
70
70
  (RESTRICT_ON_SEND.include?(ancestor.method_name) || ancestor.method?(:reverse_each))
@@ -74,8 +74,8 @@ module RuboCop
74
74
  end
75
75
 
76
76
  return unless (prev_method = node.children.first)
77
- return if !prev_method.send_type? ||
78
- prev_method.parent.block_type? || prev_method.last_argument&.block_pass_type?
77
+ return if !prev_method.call_type? || prev_method.parent.any_block_type? ||
78
+ prev_method.last_argument&.block_pass_type?
79
79
 
80
80
  detected = prev_method.method_name.to_s.start_with?('each_') unless node.method?(:each)
81
81
 
@@ -52,7 +52,7 @@ module RuboCop
52
52
  ${nil? basic_literal? const_type?})
53
53
  PATTERN
54
54
 
55
- def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler
55
+ def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler, InternalAffairs/ItblockHandler
56
56
  redundant_fetch_block_candidate?(node) do |send, body|
57
57
  return if should_not_check?(send, body)
58
58
 
@@ -15,6 +15,9 @@ module RuboCop
15
15
  # # bad
16
16
  # "#{foo} bar".dup
17
17
  #
18
+ # # bad
19
+ # String.new("#{foo} bar")
20
+ #
18
21
  # # good
19
22
  # "#{foo} bar"
20
23
  #
@@ -25,19 +28,32 @@ module RuboCop
25
28
 
26
29
  MSG = "Don't unfreeze interpolated strings as they are already unfrozen."
27
30
 
28
- RESTRICT_ON_SEND = %i[+@ dup].freeze
29
-
30
31
  minimum_target_ruby_version 3.0
31
32
 
32
- def on_send(node)
33
- return if node.arguments?
34
- return unless (receiver = node.receiver)
35
- return unless receiver.dstr_type?
36
- return if uninterpolated_string?(receiver) || uninterpolated_heredoc?(receiver)
33
+ # @!method redundant_unfreeze?(node)
34
+ def_node_matcher :redundant_unfreeze?, <<~PATTERN
35
+ {
36
+ (send dstr_type? {:+@ :dup})
37
+ (send (const nil? :String) :new dstr_type?)
38
+ }
39
+ PATTERN
40
+
41
+ def on_dstr(node)
42
+ return if uninterpolated_string?(node) || uninterpolated_heredoc?(node)
43
+ return unless redundant_unfreeze?(node.parent)
44
+
45
+ add_offense(offense_range(node.parent)) do |corrector|
46
+ corrector.replace(node.parent, node.source)
47
+ end
48
+ end
49
+
50
+ private
37
51
 
38
- add_offense(node.loc.selector) do |corrector|
39
- corrector.remove(node.loc.selector)
40
- corrector.remove(node.loc.dot) unless node.unary_operation?
52
+ def offense_range(node)
53
+ if node.method?(:new)
54
+ node.source_range.begin.join(node.loc.selector)
55
+ else
56
+ node.loc.selector
41
57
  end
42
58
  end
43
59
  end
@@ -0,0 +1,93 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Style
6
+ # Identifies places where `max_by { ... }`, `min_by { ... }`, or
7
+ # `minmax_by { ... }` can be replaced by `max`, `min`, or `minmax`.
8
+ #
9
+ # @example
10
+ # # bad
11
+ # array.max_by { |x| x }
12
+ # array.min_by { |x| x }
13
+ # array.minmax_by { |x| x }
14
+ #
15
+ # # good
16
+ # array.max
17
+ # array.min
18
+ # array.minmax
19
+ class RedundantMinMaxBy < Base
20
+ include RangeHelp
21
+ extend AutoCorrector
22
+
23
+ MSG_BLOCK = 'Use `%<replacement>s` instead of `%<original>s { |%<var>s| %<var>s }`.'
24
+ MSG_NUMBLOCK = 'Use `%<replacement>s` instead of `%<original>s { _1 }`.'
25
+ MSG_ITBLOCK = 'Use `%<replacement>s` instead of `%<original>s { it }`.'
26
+
27
+ REPLACEMENTS = { max_by: 'max', min_by: 'min', minmax_by: 'minmax' }.freeze
28
+
29
+ def on_block(node)
30
+ redundant_minmax_by_block(node) do |send, var_name|
31
+ register_offense(send, node, message_block(send, var_name))
32
+ end
33
+ end
34
+
35
+ def on_numblock(node)
36
+ redundant_minmax_by_numblock(node) do |send|
37
+ register_offense(send, node, message_numblock(send))
38
+ end
39
+ end
40
+
41
+ def on_itblock(node)
42
+ redundant_minmax_by_itblock(node) do |send|
43
+ register_offense(send, node, message_itblock(send))
44
+ end
45
+ end
46
+
47
+ private
48
+
49
+ # @!method redundant_minmax_by_block(node)
50
+ def_node_matcher :redundant_minmax_by_block, <<~PATTERN
51
+ (block $(call _ {:max_by :min_by :minmax_by}) (args (arg $_x)) (lvar _x))
52
+ PATTERN
53
+
54
+ # @!method redundant_minmax_by_numblock(node)
55
+ def_node_matcher :redundant_minmax_by_numblock, <<~PATTERN
56
+ (numblock $(call _ {:max_by :min_by :minmax_by}) 1 (lvar :_1))
57
+ PATTERN
58
+
59
+ # @!method redundant_minmax_by_itblock(node)
60
+ def_node_matcher :redundant_minmax_by_itblock, <<~PATTERN
61
+ (itblock $(call _ {:max_by :min_by :minmax_by}) _ (lvar :it))
62
+ PATTERN
63
+
64
+ def register_offense(send, node, message)
65
+ range = offense_range(send, node)
66
+
67
+ add_offense(range, message: message) do |corrector|
68
+ corrector.replace(range, REPLACEMENTS[send.method_name])
69
+ end
70
+ end
71
+
72
+ def offense_range(send, node)
73
+ range_between(send.loc.selector.begin_pos, node.loc.end.end_pos)
74
+ end
75
+
76
+ def message_block(send, var_name)
77
+ method = send.method_name
78
+ format(MSG_BLOCK, replacement: REPLACEMENTS[method], original: method, var: var_name)
79
+ end
80
+
81
+ def message_numblock(send)
82
+ method = send.method_name
83
+ format(MSG_NUMBLOCK, replacement: REPLACEMENTS[method], original: method)
84
+ end
85
+
86
+ def message_itblock(send)
87
+ method = send.method_name
88
+ format(MSG_ITBLOCK, replacement: REPLACEMENTS[method], original: method)
89
+ end
90
+ end
91
+ end
92
+ end
93
+ end
@@ -114,7 +114,7 @@ module RuboCop
114
114
  def first_arg_begins_with_hash_literal?(node)
115
115
  # Don't flag `method ({key: value})` or `method ({key: value}.method)`
116
116
  hash_literal = method_chain_begins_with_hash_literal(node.children.first)
117
- if (root_method = node.each_ancestor(:send).to_a.last)
117
+ if (root_method = node.each_ancestor(:call).to_a.last)
118
118
  parenthesized = root_method.parenthesized_call?
119
119
  end
120
120
  hash_literal && first_argument?(node) && !parentheses?(hash_literal) && !parenthesized
@@ -139,6 +139,8 @@ module RuboCop
139
139
  node = begin_node.children.first
140
140
 
141
141
  if (message = find_offense_message(begin_node, node))
142
+ return offense(begin_node, message) if message == 'block body'
143
+
142
144
  if node.range_type? && !argument_of_parenthesized_method_call?(begin_node, node)
143
145
  begin_node = begin_node.parent
144
146
  end
@@ -155,6 +157,8 @@ module RuboCop
155
157
  return 'a literal' if node.literal? && disallowed_literal?(begin_node, node)
156
158
  return 'a variable' if node.variable?
157
159
  return 'a constant' if node.const_type?
160
+ return 'block body' if begin_node.parent&.any_block_type? || body_range?(begin_node, node)
161
+
158
162
  if node.assignment? && (begin_node.parent.nil? || begin_node.parent.begin_type?)
159
163
  return 'an assignment'
160
164
  end
@@ -266,6 +270,15 @@ module RuboCop
266
270
  end
267
271
  end
268
272
 
273
+ def body_range?(begin_node, node)
274
+ return false unless node.range_type?
275
+ return false unless (parent = begin_node.parent)
276
+ return false if parent.pair_type?
277
+
278
+ (node.begin.nil? && begin_node == parent.children.first) ||
279
+ (node.end.nil? && begin_node == parent.children.last)
280
+ end
281
+
269
282
  def disallowed_one_line_pattern_matching?(begin_node, node)
270
283
  if (parent = begin_node.parent)
271
284
  return false if parent.any_def_type? && parent.endless?
@@ -308,10 +321,10 @@ module RuboCop
308
321
  end
309
322
 
310
323
  def singular_parenthesized_parent?(begin_node)
311
- return true unless begin_node.parent
312
- return false if begin_node.parent.type?(:splat, :kwsplat)
324
+ return true unless (parent = begin_node.parent)
325
+ return false if parent.type?(:splat, :kwsplat)
313
326
 
314
- parentheses?(begin_node) && begin_node.parent.children.one?
327
+ parent.children.one?
315
328
  end
316
329
 
317
330
  def only_begin_arg?(args)
@@ -319,28 +332,15 @@ module RuboCop
319
332
  end
320
333
 
321
334
  def first_argument?(node)
322
- if first_send_argument?(node) ||
323
- first_super_argument?(node) ||
324
- first_yield_argument?(node)
325
- return true
326
- end
335
+ return true if first_call_argument?(node)
327
336
 
328
337
  node.each_ancestor.any? { |ancestor| first_argument?(ancestor) }
329
338
  end
330
339
 
331
- # @!method first_send_argument?(node)
332
- def_node_matcher :first_send_argument?, <<~PATTERN
333
- ^(send _ _ equal?(%0) ...)
334
- PATTERN
335
-
336
- # @!method first_super_argument?(node)
337
- def_node_matcher :first_super_argument?, <<~PATTERN
338
- ^(super equal?(%0) ...)
339
- PATTERN
340
-
341
- # @!method first_yield_argument?(node)
342
- def_node_matcher :first_yield_argument?, <<~PATTERN
343
- ^(yield equal?(%0) ...)
340
+ # @!method first_call_argument?(node)
341
+ def_node_matcher :first_call_argument?, <<~PATTERN
342
+ {^(call _ _ equal?(%0) ...)
343
+ ^({super yield} equal?(%0) ...)}
344
344
  PATTERN
345
345
 
346
346
  def call_chain_starts_with_int?(begin_node, send_node)
@@ -90,7 +90,10 @@ module RuboCop
90
90
  def acceptable_q?(node)
91
91
  src = node.source
92
92
 
93
- return true if STRING_INTERPOLATION_REGEXP.match?(src)
93
+ # If the string contains interpolation-like syntax and would be
94
+ # converted to a double-quoted string (because it contains single
95
+ # quotes), the replacement would activate interpolation.
96
+ return true if STRING_INTERPOLATION_REGEXP.match?(src) && src.include?(SINGLE_QUOTE)
94
97
 
95
98
  src.scan(/\\./).any?(ESCAPED_NON_BACKSLASH)
96
99
  end
@@ -3,7 +3,9 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Style
6
- # Checks for redundant `return` expressions.
6
+ # Checks for redundant `return` expressions. Ruby methods
7
+ # implicitly return the value of the last evaluated expression,
8
+ # so an explicit `return` at the end of a method body is unnecessary.
7
9
  #
8
10
  # @example
9
11
  # # These bad cases should be extended to handle methods whose body is
@@ -27,11 +27,6 @@ module RuboCop
27
27
 
28
28
  MSG = 'Remove the self-assignment branch.'
29
29
 
30
- # @!method bad_method?(node)
31
- def_node_matcher :bad_method?, <<~PATTERN
32
- (send nil? :bad_method ...)
33
- PATTERN
34
-
35
30
  def on_lvasgn(node)
36
31
  expression = node.expression
37
32