rubocop 1.84.2 → 1.86.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (210) hide show
  1. checksums.yaml +4 -4
  2. data/config/default.yml +99 -16
  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 +28 -2
  6. data/lib/rubocop/cli/command/list_enabled_cops_for.rb +40 -0
  7. data/lib/rubocop/cli/command/mcp.rb +19 -0
  8. data/lib/rubocop/cli/command/show_cops.rb +2 -2
  9. data/lib/rubocop/cli/command/show_docs_url.rb +4 -8
  10. data/lib/rubocop/cli/command/suggest_extensions.rb +1 -1
  11. data/lib/rubocop/cli.rb +7 -7
  12. data/lib/rubocop/comment_config.rb +12 -15
  13. data/lib/rubocop/config.rb +14 -10
  14. data/lib/rubocop/config_finder.rb +1 -1
  15. data/lib/rubocop/config_loader_resolver.rb +2 -1
  16. data/lib/rubocop/config_obsoletion/extracted_cop.rb +4 -2
  17. data/lib/rubocop/config_store.rb +1 -1
  18. data/lib/rubocop/config_validator.rb +1 -1
  19. data/lib/rubocop/cop/autocorrect_logic.rb +2 -1
  20. data/lib/rubocop/cop/correctors/condition_corrector.rb +1 -1
  21. data/lib/rubocop/cop/correctors/multiline_literal_brace_corrector.rb +1 -5
  22. data/lib/rubocop/cop/correctors/percent_literal_corrector.rb +2 -2
  23. data/lib/rubocop/cop/correctors.rb +28 -0
  24. data/lib/rubocop/cop/documentation.rb +2 -3
  25. data/lib/rubocop/cop/exclude_limit.rb +31 -5
  26. data/lib/rubocop/cop/gemspec/require_mfa.rb +4 -4
  27. data/lib/rubocop/cop/internal_affairs/itblock_handler.rb +69 -0
  28. data/lib/rubocop/cop/internal_affairs/location_line_equality_comparison.rb +1 -0
  29. data/lib/rubocop/cop/internal_affairs.rb +1 -0
  30. data/lib/rubocop/cop/layout/argument_alignment.rb +2 -2
  31. data/lib/rubocop/cop/layout/array_alignment.rb +1 -1
  32. data/lib/rubocop/cop/layout/dot_position.rb +1 -1
  33. data/lib/rubocop/cop/layout/empty_line_after_guard_clause.rb +9 -2
  34. data/lib/rubocop/cop/layout/empty_line_between_defs.rb +1 -1
  35. data/lib/rubocop/cop/layout/empty_lines_around_attribute_accessor.rb +1 -0
  36. data/lib/rubocop/cop/layout/empty_lines_around_block_body.rb +12 -2
  37. data/lib/rubocop/cop/layout/empty_lines_around_class_body.rb +16 -2
  38. data/lib/rubocop/cop/layout/empty_lines_around_module_body.rb +16 -2
  39. data/lib/rubocop/cop/layout/end_alignment.rb +6 -3
  40. data/lib/rubocop/cop/layout/first_hash_element_indentation.rb +7 -1
  41. data/lib/rubocop/cop/layout/hash_alignment.rb +1 -1
  42. data/lib/rubocop/cop/layout/indentation_width.rb +1 -1
  43. data/lib/rubocop/cop/layout/line_length.rb +5 -3
  44. data/lib/rubocop/cop/layout/multiline_assignment_layout.rb +9 -2
  45. data/lib/rubocop/cop/layout/multiline_method_call_brace_layout.rb +1 -1
  46. data/lib/rubocop/cop/layout/multiline_method_call_indentation.rb +53 -3
  47. data/lib/rubocop/cop/layout/parameter_alignment.rb +1 -1
  48. data/lib/rubocop/cop/layout/redundant_line_break.rb +1 -1
  49. data/lib/rubocop/cop/layout/space_around_block_parameters.rb +1 -1
  50. data/lib/rubocop/cop/layout/space_around_keyword.rb +3 -1
  51. data/lib/rubocop/cop/layout/space_in_lambda_literal.rb +1 -0
  52. data/lib/rubocop/cop/lint/constant_reassignment.rb +59 -9
  53. data/lib/rubocop/cop/lint/constant_resolution.rb +1 -1
  54. data/lib/rubocop/cop/lint/data_define_override.rb +63 -0
  55. data/lib/rubocop/cop/lint/duplicate_methods.rb +55 -8
  56. data/lib/rubocop/cop/lint/empty_block.rb +1 -1
  57. data/lib/rubocop/cop/lint/empty_conditional_body.rb +6 -1
  58. data/lib/rubocop/cop/lint/empty_in_pattern.rb +8 -1
  59. data/lib/rubocop/cop/lint/empty_when.rb +8 -1
  60. data/lib/rubocop/cop/lint/interpolation_check.rb +7 -2
  61. data/lib/rubocop/cop/lint/next_without_accumulator.rb +2 -0
  62. data/lib/rubocop/cop/lint/non_deterministic_require_order.rb +3 -1
  63. data/lib/rubocop/cop/lint/number_conversion.rb +1 -1
  64. data/lib/rubocop/cop/lint/parentheses_as_grouped_expression.rb +3 -13
  65. data/lib/rubocop/cop/lint/redundant_cop_enable_directive.rb +0 -9
  66. data/lib/rubocop/cop/lint/redundant_safe_navigation.rb +23 -6
  67. data/lib/rubocop/cop/lint/require_relative_self_path.rb +2 -0
  68. data/lib/rubocop/cop/lint/safe_navigation_chain.rb +17 -0
  69. data/lib/rubocop/cop/lint/safe_navigation_consistency.rb +7 -1
  70. data/lib/rubocop/cop/lint/syntax.rb +25 -1
  71. data/lib/rubocop/cop/lint/trailing_comma_in_attribute_declaration.rb +1 -0
  72. data/lib/rubocop/cop/lint/unmodified_reduce_accumulator.rb +1 -0
  73. data/lib/rubocop/cop/lint/unreachable_pattern_branch.rb +113 -0
  74. data/lib/rubocop/cop/lint/unused_method_argument.rb +10 -0
  75. data/lib/rubocop/cop/lint/useless_assignment.rb +4 -9
  76. data/lib/rubocop/cop/lint/useless_constant_scoping.rb +4 -4
  77. data/lib/rubocop/cop/lint/useless_default_value_argument.rb +2 -0
  78. data/lib/rubocop/cop/lint/utils/nil_receiver_checker.rb +35 -9
  79. data/lib/rubocop/cop/lint/void.rb +32 -12
  80. data/lib/rubocop/cop/metrics/block_nesting.rb +23 -0
  81. data/lib/rubocop/cop/migration/department_name.rb +12 -1
  82. data/lib/rubocop/cop/mixin/check_line_breakable.rb +1 -1
  83. data/lib/rubocop/cop/mixin/check_single_line_suitability.rb +2 -2
  84. data/lib/rubocop/cop/mixin/configurable_max.rb +6 -5
  85. data/lib/rubocop/cop/mixin/hash_transform_method/autocorrection.rb +63 -0
  86. data/lib/rubocop/cop/mixin/hash_transform_method.rb +10 -60
  87. data/lib/rubocop/cop/mixin.rb +85 -0
  88. data/lib/rubocop/cop/naming/block_parameter_name.rb +1 -1
  89. data/lib/rubocop/cop/naming/memoized_instance_variable_name.rb +1 -1
  90. data/lib/rubocop/cop/offense.rb +8 -0
  91. data/lib/rubocop/cop/registry.rb +39 -37
  92. data/lib/rubocop/cop/security/eval.rb +15 -2
  93. data/lib/rubocop/cop/style/access_modifier_declarations.rb +14 -2
  94. data/lib/rubocop/cop/style/accessor_grouping.rb +4 -2
  95. data/lib/rubocop/cop/style/alias.rb +4 -1
  96. data/lib/rubocop/cop/style/and_or.rb +1 -0
  97. data/lib/rubocop/cop/style/arguments_forwarding.rb +25 -7
  98. data/lib/rubocop/cop/style/array_join.rb +4 -2
  99. data/lib/rubocop/cop/style/ascii_comments.rb +6 -3
  100. data/lib/rubocop/cop/style/attr.rb +5 -2
  101. data/lib/rubocop/cop/style/bare_percent_literals.rb +3 -1
  102. data/lib/rubocop/cop/style/begin_block.rb +3 -1
  103. data/lib/rubocop/cop/style/block_delimiters.rb +25 -33
  104. data/lib/rubocop/cop/style/case_equality.rb +4 -0
  105. data/lib/rubocop/cop/style/class_and_module_children.rb +10 -2
  106. data/lib/rubocop/cop/style/collection_compact.rb +36 -16
  107. data/lib/rubocop/cop/style/colon_method_call.rb +3 -1
  108. data/lib/rubocop/cop/style/concat_array_literals.rb +2 -0
  109. data/lib/rubocop/cop/style/conditional_assignment.rb +0 -4
  110. data/lib/rubocop/cop/style/copyright.rb +22 -11
  111. data/lib/rubocop/cop/style/date_time.rb +2 -2
  112. data/lib/rubocop/cop/style/document_dynamic_eval_definition.rb +6 -1
  113. data/lib/rubocop/cop/style/each_for_simple_loop.rb +1 -1
  114. data/lib/rubocop/cop/style/each_with_object.rb +2 -0
  115. data/lib/rubocop/cop/style/empty_block_parameter.rb +1 -1
  116. data/lib/rubocop/cop/style/empty_class_definition.rb +43 -20
  117. data/lib/rubocop/cop/style/empty_lambda_parameter.rb +1 -1
  118. data/lib/rubocop/cop/style/encoding.rb +7 -1
  119. data/lib/rubocop/cop/style/end_block.rb +3 -1
  120. data/lib/rubocop/cop/style/endless_method.rb +8 -3
  121. data/lib/rubocop/cop/style/file_open.rb +84 -0
  122. data/lib/rubocop/cop/style/for.rb +3 -0
  123. data/lib/rubocop/cop/style/format_string_token.rb +29 -2
  124. data/lib/rubocop/cop/style/global_vars.rb +5 -2
  125. data/lib/rubocop/cop/style/guard_clause.rb +9 -6
  126. data/lib/rubocop/cop/style/hash_as_last_array_item.rb +21 -5
  127. data/lib/rubocop/cop/style/hash_lookup_method.rb +19 -7
  128. data/lib/rubocop/cop/style/hash_transform_keys.rb +17 -7
  129. data/lib/rubocop/cop/style/hash_transform_values.rb +17 -7
  130. data/lib/rubocop/cop/style/if_inside_else.rb +16 -7
  131. data/lib/rubocop/cop/style/if_unless_modifier.rb +14 -3
  132. data/lib/rubocop/cop/style/if_with_semicolon.rb +7 -5
  133. data/lib/rubocop/cop/style/inline_comment.rb +4 -1
  134. data/lib/rubocop/cop/style/ip_addresses.rb +1 -2
  135. data/lib/rubocop/cop/style/magic_comment_format.rb +2 -2
  136. data/lib/rubocop/cop/style/map_join.rb +123 -0
  137. data/lib/rubocop/cop/style/method_call_with_args_parentheses/require_parentheses.rb +5 -3
  138. data/lib/rubocop/cop/style/module_member_existence_check.rb +7 -14
  139. data/lib/rubocop/cop/style/multiline_if_then.rb +3 -1
  140. data/lib/rubocop/cop/style/mutable_constant.rb +1 -1
  141. data/lib/rubocop/cop/style/nil_comparison.rb +2 -3
  142. data/lib/rubocop/cop/style/nil_lambda.rb +1 -1
  143. data/lib/rubocop/cop/style/non_nil_check.rb +5 -11
  144. data/lib/rubocop/cop/style/not.rb +2 -0
  145. data/lib/rubocop/cop/style/numeric_literals.rb +3 -2
  146. data/lib/rubocop/cop/style/one_class_per_file.rb +115 -0
  147. data/lib/rubocop/cop/style/one_line_conditional.rb +4 -3
  148. data/lib/rubocop/cop/style/parallel_assignment.rb +4 -0
  149. data/lib/rubocop/cop/style/partition_instead_of_double_select.rb +270 -0
  150. data/lib/rubocop/cop/style/percent_literal_delimiters.rb +2 -0
  151. data/lib/rubocop/cop/style/predicate_with_kind.rb +84 -0
  152. data/lib/rubocop/cop/style/proc.rb +3 -2
  153. data/lib/rubocop/cop/style/raise_args.rb +1 -1
  154. data/lib/rubocop/cop/style/reduce_to_hash.rb +200 -0
  155. data/lib/rubocop/cop/style/redundant_begin.rb +3 -3
  156. data/lib/rubocop/cop/style/redundant_each.rb +3 -3
  157. data/lib/rubocop/cop/style/redundant_fetch_block.rb +1 -1
  158. data/lib/rubocop/cop/style/redundant_interpolation_unfreeze.rb +26 -10
  159. data/lib/rubocop/cop/style/redundant_line_continuation.rb +16 -0
  160. data/lib/rubocop/cop/style/redundant_min_max_by.rb +93 -0
  161. data/lib/rubocop/cop/style/redundant_parentheses.rb +25 -22
  162. data/lib/rubocop/cop/style/redundant_percent_q.rb +4 -1
  163. data/lib/rubocop/cop/style/redundant_return.rb +3 -1
  164. data/lib/rubocop/cop/style/redundant_self.rb +2 -2
  165. data/lib/rubocop/cop/style/redundant_self_assignment_branch.rb +0 -5
  166. data/lib/rubocop/cop/style/redundant_struct_keyword_init.rb +114 -0
  167. data/lib/rubocop/cop/style/regexp_literal.rb +29 -0
  168. data/lib/rubocop/cop/style/safe_navigation.rb +7 -7
  169. data/lib/rubocop/cop/style/select_by_kind.rb +158 -0
  170. data/lib/rubocop/cop/style/select_by_range.rb +197 -0
  171. data/lib/rubocop/cop/style/select_by_regexp.rb +51 -21
  172. data/lib/rubocop/cop/style/semicolon.rb +2 -0
  173. data/lib/rubocop/cop/style/single_line_block_params.rb +2 -2
  174. data/lib/rubocop/cop/style/single_line_do_end_block.rb +1 -1
  175. data/lib/rubocop/cop/style/single_line_methods.rb +3 -1
  176. data/lib/rubocop/cop/style/sole_nested_conditional.rb +4 -2
  177. data/lib/rubocop/cop/style/special_global_vars.rb +6 -1
  178. data/lib/rubocop/cop/style/symbol_proc.rb +7 -6
  179. data/lib/rubocop/cop/style/tally_method.rb +181 -0
  180. data/lib/rubocop/cop/style/trailing_comma_in_block_args.rb +1 -1
  181. data/lib/rubocop/cop/style/trailing_method_end_statement.rb +1 -0
  182. data/lib/rubocop/cop/style/while_until_modifier.rb +16 -0
  183. data/lib/rubocop/cop/style/yoda_expression.rb +1 -1
  184. data/lib/rubocop/cop/team.rb +86 -35
  185. data/lib/rubocop/cop/variable_force/branch.rb +2 -2
  186. data/lib/rubocop/directive_comment.rb +2 -1
  187. data/lib/rubocop/formatter/disabled_config_formatter.rb +5 -2
  188. data/lib/rubocop/formatter/formatter_set.rb +1 -1
  189. data/lib/rubocop/formatter/junit_formatter.rb +1 -1
  190. data/lib/rubocop/formatter/simple_text_formatter.rb +0 -2
  191. data/lib/rubocop/formatter/worst_offenders_formatter.rb +1 -1
  192. data/lib/rubocop/formatter.rb +22 -21
  193. data/lib/rubocop/lsp/diagnostic.rb +1 -0
  194. data/lib/rubocop/lsp/routes.rb +10 -3
  195. data/lib/rubocop/lsp/runtime.rb +1 -2
  196. data/lib/rubocop/mcp/server.rb +200 -0
  197. data/lib/rubocop/options.rb +17 -4
  198. data/lib/rubocop/path_util.rb +14 -2
  199. data/lib/rubocop/plugin/loader.rb +1 -1
  200. data/lib/rubocop/result_cache.rb +22 -10
  201. data/lib/rubocop/rspec/cop_helper.rb +8 -0
  202. data/lib/rubocop/rspec/shared_contexts.rb +32 -2
  203. data/lib/rubocop/runner.rb +78 -51
  204. data/lib/rubocop/server/cache.rb +5 -7
  205. data/lib/rubocop/server/core.rb +2 -0
  206. data/lib/rubocop/target_finder.rb +14 -7
  207. data/lib/rubocop/target_ruby.rb +18 -12
  208. data/lib/rubocop/version.rb +2 -2
  209. data/lib/rubocop.rb +21 -96
  210. metadata +25 -5
@@ -128,12 +128,15 @@ module RuboCop
128
128
  if hash_pair_indented?(node, pair_ancestor, given_style)
129
129
  return check_hash_pair_indented_style(rhs, pair_ancestor)
130
130
  end
131
-
132
- return false if !pair_ancestor && not_for_this_cop?(node)
131
+ return false if skip_for_context?(node, pair_ancestor)
133
132
 
134
133
  check_regular_indentation(node, lhs, rhs, given_style)
135
134
  end
136
135
 
136
+ def skip_for_context?(node, pair_ancestor)
137
+ pair_ancestor ? inside_multiline_chain_arg?(node) : not_for_this_cop?(node)
138
+ end
139
+
137
140
  def hash_pair_aligned?(pair_ancestor, given_style)
138
141
  pair_ancestor && given_style == :aligned
139
142
  end
@@ -152,7 +155,10 @@ module RuboCop
152
155
  end
153
156
 
154
157
  def check_hash_pair_indentation(node, lhs, rhs)
155
- @base = find_hash_pair_alignment_base(node) || lhs.source_range
158
+ @base = find_hash_pair_alignment_base(node)
159
+ return false if !@base && inside_multiline_chain_arg?(node)
160
+
161
+ @base ||= first_dot_alignment_base(node, rhs) || lhs.source_range
156
162
  return if aligned_with_first_line_dot?(node, rhs)
157
163
 
158
164
  calculate_column_delta_offense(rhs, @base.column)
@@ -166,6 +172,50 @@ module RuboCop
166
172
  first_call.loc.dot.join(first_call.loc.selector)
167
173
  end
168
174
 
175
+ def first_dot_alignment_base(node, rhs)
176
+ return unless rhs.source.start_with?('.', '&.')
177
+
178
+ first_call = first_call_has_a_dot(node)
179
+ dot = first_call.loc.dot
180
+ return unless dot
181
+ return if first_call == node
182
+
183
+ after_block_base = after_multiline_block_base(first_call, node)
184
+ return after_block_base if after_block_base
185
+
186
+ return unless same_line?(dot, first_call.receiver.source_range)
187
+
188
+ dot.join(first_call.loc.selector)
189
+ end
190
+
191
+ def after_multiline_block_base(first_call, node)
192
+ return unless first_call.block_node&.multiline?
193
+
194
+ after_block = first_call.block_node.parent
195
+ return unless after_block&.call_type? && after_block.loc?(:dot) && after_block != node
196
+
197
+ after_block.loc.dot.join(after_block.loc.selector)
198
+ end
199
+
200
+ def inside_multiline_chain_arg?(node)
201
+ enclosing_call = find_enclosing_chain_call(node)
202
+ return false unless enclosing_call
203
+
204
+ !same_line?(enclosing_call.loc.selector, enclosing_call.receiver.source_range)
205
+ end
206
+
207
+ def find_enclosing_chain_call(node)
208
+ hash_ancestor = find_pair_ancestor(node).parent
209
+ enclosing_call = hash_ancestor.parent
210
+ return unless hash_arg_in_chain?(enclosing_call, hash_ancestor)
211
+
212
+ enclosing_call
213
+ end
214
+
215
+ def hash_arg_in_chain?(call, hash_node)
216
+ call&.call_type? && call.receiver != hash_node && call.loc?(:dot)
217
+ end
218
+
169
219
  def aligned_with_first_line_dot?(node, rhs)
170
220
  return false unless rhs.source.start_with?('.', '&.')
171
221
 
@@ -3,7 +3,7 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Layout
6
- # Check that the parameters on a multi-line method call or definition are aligned.
6
+ # Checks that the parameters on a multi-line method call or definition are aligned.
7
7
  #
8
8
  # To set the alignment of the first argument, use the
9
9
  # `Layout/FirstParameterIndentation` cop.
@@ -114,7 +114,7 @@ module RuboCop
114
114
 
115
115
  def other_cop_takes_precedence?(node)
116
116
  single_line_block_chain_enabled? && any_descendant?(node, :any_block) do |block_node|
117
- block_node.parent.send_type? && block_node.parent.loc.dot && !block_node.multiline?
117
+ block_node.parent.send_type? && block_node.parent.loc.dot && block_node.single_line?
118
118
  end
119
119
  end
120
120
 
@@ -29,7 +29,7 @@ module RuboCop
29
29
  include RangeHelp
30
30
  extend AutoCorrector
31
31
 
32
- def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler
32
+ def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler, InternalAffairs/ItblockHandler
33
33
  arguments = node.arguments
34
34
 
35
35
  return unless node.arguments? && pipes?(arguments)
@@ -47,9 +47,11 @@ module RuboCop
47
47
  check(node, [:operator].freeze) if node.keyword?
48
48
  end
49
49
 
50
- def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler
50
+ def on_block(node)
51
51
  check(node, %i[begin end].freeze)
52
52
  end
53
+ alias on_numblock on_block
54
+ alias on_itblock on_block
53
55
 
54
56
  def on_break(node)
55
57
  check(node, [:keyword].freeze)
@@ -26,6 +26,7 @@ module RuboCop
26
26
 
27
27
  MSG_REQUIRE_SPACE = 'Use a space between `->` and `(` in lambda literals.'
28
28
  MSG_REQUIRE_NO_SPACE = 'Do not use spaces between `->` and `(` in lambda literals.'
29
+ RESTRICT_ON_SEND = %i[lambda].freeze
29
30
 
30
31
  def on_send(node)
31
32
  return unless arrow_lambda_with_args?(node)
@@ -6,8 +6,11 @@ module RuboCop
6
6
  # Checks for constant reassignments.
7
7
  #
8
8
  # Emulates Ruby's runtime warning "already initialized constant X"
9
- # when a constant is reassigned in the same file and namespace using the
10
- # `NAME = value` syntax.
9
+ # when a constant is reassigned in the same file and namespace.
10
+ #
11
+ # The cop tracks constants defined via `NAME = value` syntax as well as
12
+ # class/module keyword definitions. It detects reassignment when a constant
13
+ # is first defined one way and then redefined using the `NAME = value` syntax.
11
14
  #
12
15
  # The cop cannot catch all offenses, like, for example, when a constant
13
16
  # is reassigned in another file, or when using metaprogramming (`Module#const_set`).
@@ -36,6 +39,14 @@ module RuboCop
36
39
  # X = :bar
37
40
  # end
38
41
  #
42
+ # # bad
43
+ # class FooError < StandardError; end
44
+ # FooError = Class.new(RuntimeError)
45
+ #
46
+ # # bad
47
+ # module M; end
48
+ # M = 1
49
+ #
39
50
  # # good - keep only one assignment
40
51
  # X = :bar
41
52
  #
@@ -69,16 +80,30 @@ module RuboCop
69
80
 
70
81
  # @!method remove_constant(node)
71
82
  def_node_matcher :remove_constant, <<~PATTERN
72
- (send _ :remove_const
83
+ (send {nil? self} :remove_const
73
84
  ({sym str} $_))
74
85
  PATTERN
75
86
 
87
+ def on_class(node)
88
+ return unless unconditional_definition?(node)
89
+
90
+ constant_definitions[definition_name(node)] ||= :class
91
+ end
92
+
93
+ def on_module(node)
94
+ return unless unconditional_definition?(node)
95
+
96
+ constant_definitions[definition_name(node)] ||= :module
97
+ end
98
+
76
99
  def on_casgn(node)
77
100
  return unless fixed_constant_path?(node)
78
101
  return unless simple_assignment?(node)
79
- return if constant_names.add?(fully_qualified_constant_name(node))
80
102
 
81
- add_offense(node, message: format(MSG, constant: node.name))
103
+ name = fully_qualified_constant_name(node)
104
+ return constant_definitions[name] = :casgn unless constant_definitions.key?(name)
105
+
106
+ add_offense(node, message: format(MSG, constant: constant_display_name(node)))
82
107
  end
83
108
 
84
109
  def on_send(node)
@@ -90,7 +115,7 @@ module RuboCop
90
115
 
91
116
  return if namespaces.none?
92
117
 
93
- constant_names.delete(fully_qualified_name_for(namespaces, constant))
118
+ constant_definitions.delete(fully_qualified_name_for(namespaces, constant))
94
119
  end
95
120
 
96
121
  private
@@ -104,7 +129,7 @@ module RuboCop
104
129
  return true if ancestor.type?(:module, :class)
105
130
 
106
131
  ancestor.begin_type? || ancestor.literal? || ancestor.casgn_type? ||
107
- freeze_method?(ancestor)
132
+ ancestor.type?(:masgn, :mlhs) || freeze_method?(ancestor)
108
133
  end
109
134
  end
110
135
 
@@ -128,6 +153,10 @@ module RuboCop
128
153
  ['', *namespaces, constant].join('::')
129
154
  end
130
155
 
156
+ def constant_display_name(node)
157
+ [*constant_namespaces(node), node.name].join('::')
158
+ end
159
+
131
160
  def constant_namespaces(node)
132
161
  node.each_path.select(&:const_type?).map(&:short_name)
133
162
  end
@@ -139,8 +168,29 @@ module RuboCop
139
168
  .reverse
140
169
  end
141
170
 
142
- def constant_names
143
- @constant_names ||= Set.new
171
+ def unconditional_definition?(node)
172
+ node.each_ancestor.all? do |ancestor|
173
+ ancestor.type?(:begin, :module, :class)
174
+ end
175
+ end
176
+
177
+ def definition_name(node)
178
+ identifier = node.identifier
179
+
180
+ if identifier.namespace&.cbase_type?
181
+ fully_qualified_name_for([], identifier.short_name)
182
+ else
183
+ namespaces = ancestor_namespaces(node) + identifier_namespaces(identifier)
184
+ fully_qualified_name_for(namespaces, identifier.short_name)
185
+ end
186
+ end
187
+
188
+ def identifier_namespaces(identifier)
189
+ identifier.each_path.select(&:const_type?).map(&:short_name)
190
+ end
191
+
192
+ def constant_definitions
193
+ @constant_definitions ||= {}
144
194
  end
145
195
  end
146
196
  end
@@ -3,7 +3,7 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Lint
6
- # Check that certain constants are fully qualified.
6
+ # Checks that certain constants are fully qualified.
7
7
  #
8
8
  # This is not enabled by default because it would mark a lot of offenses
9
9
  # unnecessarily.
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Lint
6
+ # Checks unexpected overrides of the `Data` built-in methods
7
+ # via `Data.define`.
8
+ #
9
+ # @example
10
+ # # bad
11
+ # Bad = Data.define(:members, :clone, :to_s)
12
+ # b = Bad.new(members: [], clone: true, to_s: 'bad')
13
+ # b.members #=> [] (overriding `Data#members`)
14
+ # b.clone #=> true (overriding `Object#clone`)
15
+ # b.to_s #=> "bad" (overriding `Data#to_s`)
16
+ #
17
+ # # good
18
+ # Good = Data.define(:id, :name)
19
+ # g = Good.new(id: 1, name: "foo")
20
+ # g.members #=> [:id, :name]
21
+ # g.clone #=> #<data Good id=1, name="foo">
22
+ #
23
+ class DataDefineOverride < Base
24
+ MSG = '`%<member_name>s` member overrides `Data#%<method_name>s` and it may be unexpected.'
25
+ RESTRICT_ON_SEND = %i[define].freeze
26
+
27
+ # This is based on `Data.define.instance_methods.sort` in Ruby 4.0.0.
28
+ DATA_METHOD_NAMES = %i[
29
+ ! != !~ <=> == === __id__ __send__ class clone deconstruct deconstruct_keys
30
+ define_singleton_method display dup enum_for eql? equal? extend freeze frozen? hash
31
+ inspect instance_eval instance_exec instance_of? instance_variable_defined?
32
+ instance_variable_get instance_variable_set instance_variables is_a? itself kind_of?
33
+ members method methods nil? object_id private_methods protected_methods
34
+ public_method public_methods public_send remove_instance_variable respond_to? send
35
+ singleton_class singleton_method singleton_methods tap then to_enum to_h to_s with
36
+ yield_self
37
+ ].freeze
38
+ MEMBER_NAME_TYPES = %i[sym str].freeze
39
+
40
+ # @!method data_define(node)
41
+ def_node_matcher :data_define, <<~PATTERN
42
+ (send
43
+ (const {nil? cbase} :Data) :define ...)
44
+ PATTERN
45
+
46
+ def on_send(node)
47
+ return unless data_define(node)
48
+
49
+ node.arguments.each do |arg|
50
+ next unless MEMBER_NAME_TYPES.include?(arg.type)
51
+
52
+ member_name = arg.value
53
+
54
+ next unless DATA_METHOD_NAMES.include?(member_name.to_sym)
55
+
56
+ message = format(MSG, member_name: member_name.inspect, method_name: member_name.to_s)
57
+ add_offense(arg, message: message)
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
@@ -197,6 +197,13 @@ module RuboCop
197
197
  # @!method sym_name(node)
198
198
  def_node_matcher :sym_name, '(sym $_name)'
199
199
 
200
+ # @!method class_or_module_new_block?(node)
201
+ def_node_matcher :class_or_module_new_block?, <<~PATTERN
202
+ (block
203
+ (send (const _ {:Class :Module}) :new ...)
204
+ ...)
205
+ PATTERN
206
+
200
207
  def on_send(node) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
201
208
  name, original_name = alias_method?(node)
202
209
 
@@ -233,9 +240,12 @@ module RuboCop
233
240
 
234
241
  def check_self_receiver(node, name)
235
242
  enclosing = node.parent_module_name
236
- return unless enclosing
237
-
238
- found_method(node, "#{enclosing}.#{name}")
243
+ if enclosing
244
+ found_method(node, "#{enclosing}.#{name}")
245
+ elsif (anon_block = anonymous_class_block(node))
246
+ scope = qualified_name(anon_block.parent_module_name, nil, 'Object')
247
+ found_method(node, "#{scope}.#{name}", scope_id: anon_block_scope_id(anon_block))
248
+ end
239
249
  end
240
250
 
241
251
  def inside_condition?(node)
@@ -274,16 +284,50 @@ module RuboCop
274
284
  end
275
285
 
276
286
  def found_instance_method(node, name)
277
- return found_sclass_method(node, name) unless (scope = node.parent_module_name)
287
+ if (scope = node.parent_module_name)
288
+ found_method(node, "#{humanize_scope(scope)}#{name}")
289
+ elsif (anon_block = anonymous_class_block(node))
290
+ base = qualified_name(anon_block.parent_module_name, nil, 'Object')
291
+ scope = node.each_ancestor(:sclass).any? ? "#<Class:#{base}>" : base
292
+ found_method(
293
+ node, "#{humanize_scope(scope)}#{name}", scope_id: anon_block_scope_id(anon_block)
294
+ )
295
+ else
296
+ found_sclass_method(node, name)
297
+ end
298
+ end
278
299
 
279
- # Humanize the scope
300
+ def humanize_scope(scope)
280
301
  scope = scope.sub(
281
302
  /(?:(?<name>.*)::)#<Class:\k<name>>|#<Class:(?<name>.*)>(?:::)?/,
282
303
  '\k<name>.'
283
304
  )
284
- scope << '#' unless scope.end_with?('.')
305
+ scope.end_with?('.') ? scope : "#{scope}#"
306
+ end
307
+
308
+ def anonymous_class_block(node)
309
+ first_block = node.each_ancestor(:block).first
310
+ return unless class_or_module_new_block?(first_block)
311
+ return if first_block.parent&.type?(:lvasgn)
312
+ return if node.each_ancestor(:sclass).any? { |s| !s.children.first.self_type? }
313
+
314
+ first_block
315
+ end
316
+
317
+ def anon_block_scope_id(anon_block)
318
+ parent = anon_block.parent
319
+ return unless parent&.type?(:any_block, :begin, :call, :casgn, :any_def)
320
+
321
+ if (receiver = named_receiver(parent))
322
+ "#{receiver.source}.#{parent.method_name}"
323
+ elsif !parent.begin_type? || parent.parent&.any_block_type?
324
+ source_location(anon_block)
325
+ end
326
+ end
285
327
 
286
- found_method(node, "#{scope}#{name}")
328
+ def named_receiver(node)
329
+ receiver = node.receiver
330
+ receiver unless class_or_module_new_block?(receiver)
287
331
  end
288
332
 
289
333
  def found_sclass_method(node, name)
@@ -296,8 +340,10 @@ module RuboCop
296
340
  found_method(node, "#{singleton_receiver_node.method_name}.#{name}")
297
341
  end
298
342
 
299
- def found_method(node, method_name)
343
+ # rubocop:disable Metrics/AbcSize
344
+ def found_method(node, method_name, scope_id: nil)
300
345
  key = method_key(node, method_name)
346
+ key = "#{key}@#{scope_id}" if scope_id
301
347
  scope = node.each_ancestor(:rescue, :ensure).first&.type
302
348
 
303
349
  if @definitions.key?(key)
@@ -314,6 +360,7 @@ module RuboCop
314
360
  @definitions[key] = node
315
361
  end
316
362
  end
363
+ # rubocop:enable Metrics/AbcSize
317
364
 
318
365
  def method_key(node, method_name)
319
366
  if (ancestor_def = node.each_ancestor(:any_def).first)
@@ -63,7 +63,7 @@ module RuboCop
63
63
  class EmptyBlock < Base
64
64
  MSG = 'Empty block detected.'
65
65
 
66
- def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler
66
+ def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler, InternalAffairs/ItblockHandler
67
67
  return if node.body
68
68
  return if allow_empty_lambdas? && node.lambda_or_proc?
69
69
  return if cop_config['AllowComments'] && allow_comment?(node)
@@ -70,7 +70,7 @@ module RuboCop
70
70
 
71
71
  def on_if(node)
72
72
  return if node.body || same_line?(node.loc.begin, node.loc.end)
73
- return if cop_config['AllowComments'] && contains_comments?(node)
73
+ return if allow_comments?(node)
74
74
 
75
75
  range = offense_range(node)
76
76
 
@@ -83,6 +83,11 @@ module RuboCop
83
83
 
84
84
  private
85
85
 
86
+ def allow_comments?(node)
87
+ cop_config['AllowComments'] && contains_comments?(node) &&
88
+ !comments_contain_disables?(node, name)
89
+ end
90
+
86
91
  def offense_range(node)
87
92
  if node.loc.else
88
93
  node.source_range.begin.join(node.loc.else.begin)
@@ -53,11 +53,18 @@ module RuboCop
53
53
  def on_case_match(node)
54
54
  node.in_pattern_branches.each do |branch|
55
55
  next if branch.body
56
- next if cop_config['AllowComments'] && contains_comments?(branch)
56
+ next if allow_comments?(branch)
57
57
 
58
58
  add_offense(branch)
59
59
  end
60
60
  end
61
+
62
+ private
63
+
64
+ def allow_comments?(node)
65
+ cop_config['AllowComments'] && contains_comments?(node) &&
66
+ !comments_contain_disables?(node, name)
67
+ end
61
68
  end
62
69
  end
63
70
  end
@@ -50,11 +50,18 @@ module RuboCop
50
50
  def on_case(node)
51
51
  node.when_branches.each do |when_node|
52
52
  next if when_node.body
53
- next if cop_config['AllowComments'] && contains_comments?(when_node)
53
+ next if allow_comments?(when_node)
54
54
 
55
55
  add_offense(when_node)
56
56
  end
57
57
  end
58
+
59
+ private
60
+
61
+ def allow_comments?(node)
62
+ cop_config['AllowComments'] && contains_comments?(node) &&
63
+ !comments_contain_disables?(node, name)
64
+ end
58
65
  end
59
66
  end
60
67
  end
@@ -54,9 +54,14 @@ module RuboCop
54
54
  end
55
55
 
56
56
  def valid_syntax?(node)
57
- double_quoted_string = node.source.gsub(/\A'|'\z/, '"')
57
+ double_quoted_string = if node.source.include?('"')
58
+ node.source.sub(/\A'/, '%{').sub(/'\z/, '}')
59
+ else
60
+ node.source.gsub(/\A'|'\z/, '"')
61
+ end
58
62
 
59
- parse(double_quoted_string).valid_syntax?
63
+ processed_source = parse(double_quoted_string)
64
+ processed_source.valid_syntax? && processed_source.ast.dstr_type?
60
65
  end
61
66
  end
62
67
  end
@@ -31,6 +31,7 @@ module RuboCop
31
31
  end
32
32
  end
33
33
  alias on_numblock on_block
34
+ alias on_itblock on_block
34
35
 
35
36
  private
36
37
 
@@ -39,6 +40,7 @@ module RuboCop
39
40
  {
40
41
  (block (call _recv {:reduce :inject} !sym) _blockargs $(begin ...))
41
42
  (numblock (call _recv {:reduce :inject} !sym) _argscount $(begin ...))
43
+ (itblock (call _recv {:reduce :inject} !sym) _argscount $(begin ...))
42
44
  }
43
45
  PATTERN
44
46
 
@@ -65,7 +65,9 @@ module RuboCop
65
65
 
66
66
  maximum_target_ruby_version 2.7
67
67
 
68
- def on_block(node)
68
+ # NOTE: itblock is not handled because this cop is limited to Ruby <= 2.7
69
+ # via `maximum_target_ruby_version`, so itblock nodes (Ruby 3.4+) are never encountered.
70
+ def on_block(node) # rubocop:disable InternalAffairs/ItblockHandler
69
71
  return unless node.body
70
72
  return unless unsorted_dir_loop?(node.send_node)
71
73
 
@@ -162,7 +162,7 @@ module RuboCop
162
162
  end
163
163
 
164
164
  def allow_receiver?(receiver)
165
- if receiver.numeric_type? || (receiver.send_type? &&
165
+ if receiver.numeric_type? || (receiver.call_type? &&
166
166
  (conversion_method?(receiver.method_name) ||
167
167
  allowed_method_name?(receiver.method_name)))
168
168
  true
@@ -63,19 +63,9 @@ module RuboCop
63
63
  end
64
64
 
65
65
  def spaces_before_left_parenthesis(node)
66
- receiver = node.receiver
67
- receiver_length = if receiver
68
- receiver.source.length
69
- else
70
- 0
71
- end
72
- without_receiver = node.source[receiver_length..]
73
-
74
- # Escape question mark if any.
75
- method_regexp = Regexp.escape(node.method_name)
76
-
77
- match = without_receiver.match(/^\s*&?\.?\s*#{method_regexp}(\s+)\(/)
78
- match ? match.captures[0].length : 0
66
+ return 0 if node.parenthesized?
67
+
68
+ node.first_argument.source_range.begin_pos - node.loc.selector.end_pos
79
69
  end
80
70
 
81
71
  def space_range(expr, space_length)
@@ -1,11 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # The Lint/RedundantCopEnableDirective and Lint/RedundantCopDisableDirective
4
- # cops need to be disabled so as to be able to provide a (bad) example of an
5
- # unneeded enable.
6
-
7
- # rubocop:disable Lint/RedundantCopEnableDirective
8
- # rubocop:disable Lint/RedundantCopDisableDirective
9
3
  module RuboCop
10
4
  module Cop
11
5
  module Lint
@@ -130,6 +124,3 @@ module RuboCop
130
124
  end
131
125
  end
132
126
  end
133
-
134
- # rubocop:enable Lint/RedundantCopDisableDirective
135
- # rubocop:enable Lint/RedundantCopEnableDirective
@@ -62,6 +62,22 @@ module RuboCop
62
62
  # do_something if attrs.respond_to?(:[])
63
63
  #
64
64
  # # bad
65
+ # foo&.bar ? foo&.bar.baz : qux
66
+ #
67
+ # # good
68
+ # foo&.bar ? foo.bar.baz : qux
69
+ #
70
+ # # bad
71
+ # if foo&.bar
72
+ # foo&.bar.baz
73
+ # end
74
+ #
75
+ # # good
76
+ # if foo&.bar
77
+ # foo.bar.baz
78
+ # end
79
+ #
80
+ # # bad
65
81
  # while node&.is_a?(BeginNode)
66
82
  # node = node.parent
67
83
  # end
@@ -71,8 +87,9 @@ module RuboCop
71
87
  # node = node.parent
72
88
  # end
73
89
  #
74
- # # good - without `&.` this will always return `true`
90
+ # # good - without `&.` this changes the return value for `nil`
75
91
  # foo&.respond_to?(:to_a)
92
+ # foo&.respond_to?(:class)
76
93
  #
77
94
  # # bad - for `nil`s conversion methods return default values for the type
78
95
  # foo&.to_h || {}
@@ -151,15 +168,15 @@ module RuboCop
151
168
  MSG_NON_NIL = 'Redundant safe navigation on non-nil receiver (detected by analyzing ' \
152
169
  'previous code/method invocations).'
153
170
 
154
- NIL_SPECIFIC_METHODS = (nil.methods - Object.new.methods).to_set.freeze
171
+ NIL_METHODS = nil.methods.to_set.freeze
155
172
 
156
173
  SNAKE_CASE = /\A[[:digit:][:upper:]_]+\z/.freeze
157
174
 
158
175
  GUARANTEED_INSTANCE_METHODS = %i[to_s to_i to_f to_a to_h].freeze
159
176
 
160
- # @!method respond_to_nil_specific_method?(node)
161
- def_node_matcher :respond_to_nil_specific_method?, <<~PATTERN
162
- (csend _ :respond_to? (sym %NIL_SPECIFIC_METHODS))
177
+ # @!method respond_to_nil_method?(node)
178
+ def_node_matcher :respond_to_nil_method?, <<~PATTERN
179
+ (csend _ :respond_to? (sym %NIL_METHODS))
163
180
  PATTERN
164
181
 
165
182
  # @!method conversion_with_default?(node)
@@ -189,7 +206,7 @@ module RuboCop
189
206
 
190
207
  unless assume_receiver_instance_exists?(node.receiver)
191
208
  return if !guaranteed_instance?(node.receiver) && !check?(node)
192
- return if respond_to_nil_specific_method?(node)
209
+ return if respond_to_nil_method?(node)
193
210
  end
194
211
 
195
212
  add_offense(range) { |corrector| corrector.replace(range, '.') }