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
@@ -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
@@ -82,6 +82,7 @@ module RuboCop
82
82
  tIDENTIFIER kBREAK kNEXT kRETURN kSUPER kYIELD
83
83
  ].freeze
84
84
  ARITHMETIC_OPERATOR_TOKENS = %i[tDIVIDE tDSTAR tMINUS tPERCENT tPLUS tSTAR2].freeze
85
+ STRING_LITERAL_BEGIN_TOKENS = %i[tSTRING_BEG tXSTRING_BEG tREGEXP_BEG tSYMBEG].freeze
85
86
 
86
87
  def on_new_investigation
87
88
  return unless processed_source.ast
@@ -105,6 +106,7 @@ module RuboCop
105
106
  string_concatenation?(range.source_line) ||
106
107
  start_with_arithmetic_operator?(range) ||
107
108
  inside_string_literal_or_method_with_argument?(range) ||
109
+ inside_string_literal_with_interpolation?(range) ||
108
110
  leading_dot_method_chain_with_blank_line?(range)
109
111
  end
110
112
 
@@ -132,6 +134,20 @@ module RuboCop
132
134
  end
133
135
  end
134
136
 
137
+ def inside_string_literal_with_interpolation?(range)
138
+ string_depth = 0
139
+ processed_source.tokens.each do |token|
140
+ break if token.pos.begin_pos >= range.begin_pos
141
+
142
+ if STRING_LITERAL_BEGIN_TOKENS.include?(token.type)
143
+ string_depth += 1
144
+ elsif token.type == :tSTRING_END
145
+ string_depth -= 1
146
+ end
147
+ end
148
+ string_depth.positive?
149
+ end
150
+
135
151
  def leading_dot_method_chain_with_blank_line?(range)
136
152
  return false unless range.source_line.strip.start_with?('.', '&.')
137
153
 
@@ -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,18 @@ module RuboCop
266
270
  end
267
271
  end
268
272
 
273
+ # rubocop:disable Metrics/CyclomaticComplexity
274
+ def body_range?(begin_node, node)
275
+ return false if begin_node.chained?
276
+ return false unless node.range_type?
277
+ return false unless (parent = begin_node.parent)
278
+ return false unless parent.begin_type?
279
+
280
+ (node.begin.nil? && begin_node == parent.children.first) ||
281
+ (node.end.nil? && begin_node == parent.children.last)
282
+ end
283
+ # rubocop:enable Metrics/CyclomaticComplexity
284
+
269
285
  def disallowed_one_line_pattern_matching?(begin_node, node)
270
286
  if (parent = begin_node.parent)
271
287
  return false if parent.any_def_type? && parent.endless?
@@ -308,10 +324,10 @@ module RuboCop
308
324
  end
309
325
 
310
326
  def singular_parenthesized_parent?(begin_node)
311
- return true unless begin_node.parent
312
- return false if begin_node.parent.type?(:splat, :kwsplat)
327
+ return true unless (parent = begin_node.parent)
328
+ return false if parent.type?(:splat, :kwsplat)
313
329
 
314
- parentheses?(begin_node) && begin_node.parent.children.one?
330
+ parent.children.one?
315
331
  end
316
332
 
317
333
  def only_begin_arg?(args)
@@ -319,28 +335,15 @@ module RuboCop
319
335
  end
320
336
 
321
337
  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
338
+ return true if first_call_argument?(node)
327
339
 
328
340
  node.each_ancestor.any? { |ancestor| first_argument?(ancestor) }
329
341
  end
330
342
 
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) ...)
343
+ # @!method first_call_argument?(node)
344
+ def_node_matcher :first_call_argument?, <<~PATTERN
345
+ {^(call _ _ equal?(%0) ...)
346
+ ^({super yield} equal?(%0) ...)}
344
347
  PATTERN
345
348
 
346
349
  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
 
@@ -0,0 +1,114 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Style
6
+ # Checks for redundant `keyword_init` option for `Struct.new`.
7
+ #
8
+ # Since Ruby 3.2, `keyword_init` in `Struct.new` defaults to `nil` behavior.
9
+ # Therefore, this cop detects and autocorrects redundant `keyword_init: nil`
10
+ # and `keyword_init: true` in `Struct.new`.
11
+ #
12
+ # This cop is disabled by default because `keyword_init: true` is not purely
13
+ # redundant. It changes behavior in the following ways:
14
+ #
15
+ # - `Struct#keyword_init?` returns `true` instead of `nil`.
16
+ # - A `Struct` with `keyword_init: true` accepts a `Hash` argument and
17
+ # expands it as keyword arguments, whereas without it the `Hash` is
18
+ # treated as a positional argument.
19
+ # - `keyword_init: true` raises an `ArgumentError` for positional arguments,
20
+ # enforcing keyword-only initialization.
21
+ #
22
+ # @safety
23
+ # This autocorrect is unsafe because when the value of `keyword_init` changes
24
+ # from `true` to `nil`, the return value of `Struct#keyword_init?` changes.
25
+ #
26
+ # @example
27
+ #
28
+ # # bad
29
+ # Struct.new(:foo, keyword_init: nil)
30
+ # Struct.new(:foo, keyword_init: true)
31
+ #
32
+ # # good
33
+ # Struct.new(:foo)
34
+ #
35
+ class RedundantStructKeywordInit < Base
36
+ extend AutoCorrector
37
+ extend TargetRubyVersion
38
+
39
+ MSG = 'Remove the redundant `keyword_init: %<value>s`.'
40
+ RESTRICT_ON_SEND = %i[new].freeze
41
+
42
+ minimum_target_ruby_version 3.2
43
+
44
+ # @!method struct_new?(node)
45
+ def_node_matcher :struct_new?, <<~PATTERN
46
+ (call (const {nil? cbase} :Struct) :new ...)
47
+ PATTERN
48
+
49
+ # @!method keyword_init?(node)
50
+ def_node_matcher :keyword_init?, <<~PATTERN
51
+ {#redundant_keyword_init? #keyword_init_false?}
52
+ PATTERN
53
+
54
+ # @!method redundant_keyword_init?(node)
55
+ def_node_matcher :redundant_keyword_init?, <<~PATTERN
56
+ (pair (sym :keyword_init) {(true) (nil)})
57
+ PATTERN
58
+
59
+ # @!method keyword_init_false?(node)
60
+ def_node_matcher :keyword_init_false?, <<~PATTERN
61
+ (pair (sym :keyword_init) (false))
62
+ PATTERN
63
+
64
+ def on_send(node)
65
+ return if !struct_new?(node) || node.arguments.none? || !node.last_argument.hash_type?
66
+
67
+ keyword_init_nodes = select_keyword_init_nodes(node)
68
+ return if keyword_init_nodes.any? { |node| keyword_init_false?(node) }
69
+
70
+ redundant_keyword_init_nodes = select_redundant_keyword_init_nodes(keyword_init_nodes)
71
+
72
+ redundant_keyword_init_nodes.each do |redundant_keyword_init|
73
+ register_offense(redundant_keyword_init)
74
+ end
75
+ end
76
+ alias on_csend on_send
77
+
78
+ private
79
+
80
+ def select_keyword_init_nodes(node)
81
+ node.last_argument.pairs.select do |pair|
82
+ keyword_init?(pair)
83
+ end
84
+ end
85
+
86
+ def select_redundant_keyword_init_nodes(keyword_init_nodes)
87
+ keyword_init_nodes.select do |keyword_init_node|
88
+ redundant_keyword_init?(keyword_init_node)
89
+ end
90
+ end
91
+
92
+ def register_offense(keyword_init)
93
+ message = format(MSG, value: keyword_init.value.source)
94
+
95
+ add_offense(keyword_init, message: message) do |corrector|
96
+ range = range(keyword_init)
97
+
98
+ corrector.remove(range)
99
+ end
100
+ end
101
+
102
+ def range(redundant_keyword_init)
103
+ if redundant_keyword_init.parent.left_siblings.last.is_a?(AST::Node)
104
+ beginning_of_range = redundant_keyword_init.parent.left_siblings.last.source_range.end
105
+
106
+ beginning_of_range.join(redundant_keyword_init.source_range.end)
107
+ else
108
+ redundant_keyword_init
109
+ end
110
+ end
111
+ end
112
+ end
113
+ end
114
+ end
@@ -367,13 +367,13 @@ module RuboCop
367
367
  def unsafe_method_used?(node, method_chain, method)
368
368
  return true if unsafe_method?(node, method)
369
369
 
370
- method.each_ancestor(:send).any? do |ancestor|
371
- break true unless config.cop_enabled?('Lint/SafeNavigationChain')
372
-
373
- break true if unsafe_method?(node, ancestor)
374
- break true if nil_methods.include?(ancestor.method_name)
375
- break false if ancestor == method_chain
370
+ method.each_ancestor(:send) do |ancestor|
371
+ return true unless config.cop_enabled?('Lint/SafeNavigationChain')
372
+ return true if unsafe_method?(node, ancestor)
373
+ return true if nil_methods.include?(ancestor.method_name)
374
+ return false if ancestor == method_chain
376
375
  end
376
+ false
377
377
  end
378
378
 
379
379
  def unsafe_method?(node, send_node)
@@ -409,7 +409,7 @@ module RuboCop
409
409
  start_method,
410
410
  method_chain)
411
411
  start_method.each_ancestor do |ancestor|
412
- break unless %i[send block].include?(ancestor.type)
412
+ break unless ancestor.type?(:call, :any_block)
413
413
  next if !ancestor.send_type? || ancestor.operator_method?
414
414
 
415
415
  corrector.insert_before(ancestor.loc.dot, '&')