rubocop 1.79.2 → 1.87.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 (376) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE.txt +1 -1
  3. data/README.md +2 -2
  4. data/config/default.yml +185 -20
  5. data/config/obsoletion.yml +9 -0
  6. data/exe/rubocop +1 -8
  7. data/lib/rubocop/cache_config.rb +29 -0
  8. data/lib/rubocop/cli/command/auto_generate_config.rb +30 -4
  9. data/lib/rubocop/cli/command/list_enabled_cops_for.rb +40 -0
  10. data/lib/rubocop/cli/command/lsp.rb +1 -1
  11. data/lib/rubocop/cli/command/mcp.rb +19 -0
  12. data/lib/rubocop/cli/command/show_cops.rb +2 -2
  13. data/lib/rubocop/cli/command/show_docs_url.rb +4 -8
  14. data/lib/rubocop/cli/command/suggest_extensions.rb +1 -1
  15. data/lib/rubocop/cli.rb +35 -9
  16. data/lib/rubocop/comment_config.rb +59 -17
  17. data/lib/rubocop/config.rb +14 -10
  18. data/lib/rubocop/config_finder.rb +1 -1
  19. data/lib/rubocop/config_loader.rb +37 -23
  20. data/lib/rubocop/config_loader_resolver.rb +20 -10
  21. data/lib/rubocop/config_obsoletion/extracted_cop.rb +4 -2
  22. data/lib/rubocop/config_store.rb +7 -2
  23. data/lib/rubocop/config_validator.rb +1 -1
  24. data/lib/rubocop/cop/autocorrect_logic.rb +10 -5
  25. data/lib/rubocop/cop/base.rb +8 -2
  26. data/lib/rubocop/cop/bundler/gem_version.rb +28 -28
  27. data/lib/rubocop/cop/bundler/ordered_gems.rb +1 -2
  28. data/lib/rubocop/cop/correctors/alignment_corrector.rb +26 -7
  29. data/lib/rubocop/cop/correctors/condition_corrector.rb +1 -1
  30. data/lib/rubocop/cop/correctors/for_to_each_corrector.rb +7 -2
  31. data/lib/rubocop/cop/correctors/multiline_literal_brace_corrector.rb +1 -5
  32. data/lib/rubocop/cop/correctors/parentheses_corrector.rb +33 -2
  33. data/lib/rubocop/cop/correctors/percent_literal_corrector.rb +2 -2
  34. data/lib/rubocop/cop/correctors.rb +28 -0
  35. data/lib/rubocop/cop/documentation.rb +2 -3
  36. data/lib/rubocop/cop/exclude_limit.rb +31 -5
  37. data/lib/rubocop/cop/gemspec/duplicated_assignment.rb +2 -2
  38. data/lib/rubocop/cop/gemspec/ordered_dependencies.rb +1 -2
  39. data/lib/rubocop/cop/gemspec/require_mfa.rb +5 -5
  40. data/lib/rubocop/cop/gemspec/ruby_version_globals_usage.rb +12 -7
  41. data/lib/rubocop/cop/internal_affairs/example_heredoc_delimiter.rb +8 -8
  42. data/lib/rubocop/cop/internal_affairs/itblock_handler.rb +69 -0
  43. data/lib/rubocop/cop/internal_affairs/location_exists.rb +28 -2
  44. data/lib/rubocop/cop/internal_affairs/location_line_equality_comparison.rb +1 -0
  45. data/lib/rubocop/cop/internal_affairs/node_matcher_directive.rb +9 -9
  46. data/lib/rubocop/cop/internal_affairs/node_pattern_groups/ast_processor.rb +1 -1
  47. data/lib/rubocop/cop/internal_affairs/node_pattern_groups.rb +3 -1
  48. data/lib/rubocop/cop/internal_affairs/on_send_without_on_csend.rb +1 -1
  49. data/lib/rubocop/cop/internal_affairs/useless_message_assertion.rb +4 -4
  50. data/lib/rubocop/cop/internal_affairs.rb +1 -0
  51. data/lib/rubocop/cop/layout/argument_alignment.rb +2 -2
  52. data/lib/rubocop/cop/layout/array_alignment.rb +1 -1
  53. data/lib/rubocop/cop/layout/begin_end_alignment.rb +1 -1
  54. data/lib/rubocop/cop/layout/case_indentation.rb +3 -1
  55. data/lib/rubocop/cop/layout/class_structure.rb +14 -7
  56. data/lib/rubocop/cop/layout/dot_position.rb +2 -2
  57. data/lib/rubocop/cop/layout/empty_line_after_guard_clause.rb +26 -7
  58. data/lib/rubocop/cop/layout/empty_line_between_defs.rb +31 -13
  59. data/lib/rubocop/cop/layout/empty_lines_after_module_inclusion.rb +2 -2
  60. data/lib/rubocop/cop/layout/empty_lines_around_attribute_accessor.rb +1 -0
  61. data/lib/rubocop/cop/layout/empty_lines_around_block_body.rb +12 -2
  62. data/lib/rubocop/cop/layout/empty_lines_around_class_body.rb +16 -2
  63. data/lib/rubocop/cop/layout/empty_lines_around_module_body.rb +16 -2
  64. data/lib/rubocop/cop/layout/end_alignment.rb +10 -3
  65. data/lib/rubocop/cop/layout/first_argument_indentation.rb +34 -1
  66. data/lib/rubocop/cop/layout/first_array_element_line_break.rb +26 -0
  67. data/lib/rubocop/cop/layout/first_hash_element_indentation.rb +7 -1
  68. data/lib/rubocop/cop/layout/first_hash_element_line_break.rb +25 -25
  69. data/lib/rubocop/cop/layout/hash_alignment.rb +3 -6
  70. data/lib/rubocop/cop/layout/heredoc_argument_closing_parenthesis.rb +2 -2
  71. data/lib/rubocop/cop/layout/heredoc_indentation.rb +33 -3
  72. data/lib/rubocop/cop/layout/indentation_style.rb +1 -1
  73. data/lib/rubocop/cop/layout/indentation_width.rb +123 -7
  74. data/lib/rubocop/cop/layout/line_continuation_spacing.rb +1 -1
  75. data/lib/rubocop/cop/layout/line_length.rb +26 -9
  76. data/lib/rubocop/cop/layout/multiline_array_brace_layout.rb +57 -57
  77. data/lib/rubocop/cop/layout/multiline_assignment_layout.rb +9 -2
  78. data/lib/rubocop/cop/layout/multiline_block_layout.rb +2 -0
  79. data/lib/rubocop/cop/layout/multiline_hash_brace_layout.rb +56 -56
  80. data/lib/rubocop/cop/layout/multiline_method_call_brace_layout.rb +1 -1
  81. data/lib/rubocop/cop/layout/multiline_method_call_indentation.rb +229 -39
  82. data/lib/rubocop/cop/layout/multiline_operation_indentation.rb +8 -4
  83. data/lib/rubocop/cop/layout/parameter_alignment.rb +1 -1
  84. data/lib/rubocop/cop/layout/redundant_line_break.rb +3 -1
  85. data/lib/rubocop/cop/layout/rescue_ensure_alignment.rb +13 -3
  86. data/lib/rubocop/cop/layout/space_after_comma.rb +2 -10
  87. data/lib/rubocop/cop/layout/space_after_semicolon.rb +1 -1
  88. data/lib/rubocop/cop/layout/space_around_block_parameters.rb +1 -1
  89. data/lib/rubocop/cop/layout/space_around_keyword.rb +4 -2
  90. data/lib/rubocop/cop/layout/space_before_brackets.rb +1 -1
  91. data/lib/rubocop/cop/layout/space_in_lambda_literal.rb +9 -8
  92. data/lib/rubocop/cop/layout/trailing_whitespace.rb +1 -1
  93. data/lib/rubocop/cop/lint/ambiguous_block_association.rb +1 -1
  94. data/lib/rubocop/cop/lint/circular_argument_reference.rb +47 -3
  95. data/lib/rubocop/cop/lint/constant_overwritten_in_rescue.rb +4 -3
  96. data/lib/rubocop/cop/lint/constant_reassignment.rb +93 -11
  97. data/lib/rubocop/cop/lint/constant_resolution.rb +6 -6
  98. data/lib/rubocop/cop/lint/cop_directive_syntax.rb +14 -8
  99. data/lib/rubocop/cop/lint/data_define_override.rb +63 -0
  100. data/lib/rubocop/cop/lint/debugger.rb +0 -2
  101. data/lib/rubocop/cop/lint/deprecated_constants.rb +1 -1
  102. data/lib/rubocop/cop/lint/deprecated_open_ssl_constant.rb +4 -1
  103. data/lib/rubocop/cop/lint/duplicate_match_pattern.rb +4 -4
  104. data/lib/rubocop/cop/lint/duplicate_methods.rb +111 -12
  105. data/lib/rubocop/cop/lint/duplicate_regexp_character_class_element.rb +5 -42
  106. data/lib/rubocop/cop/lint/else_layout.rb +19 -0
  107. data/lib/rubocop/cop/lint/empty_block.rb +1 -1
  108. data/lib/rubocop/cop/lint/empty_conditional_body.rb +6 -1
  109. data/lib/rubocop/cop/lint/empty_in_pattern.rb +8 -1
  110. data/lib/rubocop/cop/lint/empty_interpolation.rb +11 -0
  111. data/lib/rubocop/cop/lint/empty_when.rb +8 -1
  112. data/lib/rubocop/cop/lint/erb_new_arguments.rb +1 -1
  113. data/lib/rubocop/cop/lint/float_comparison.rb +1 -1
  114. data/lib/rubocop/cop/lint/interpolation_check.rb +7 -2
  115. data/lib/rubocop/cop/lint/literal_as_condition.rb +5 -1
  116. data/lib/rubocop/cop/lint/literal_in_interpolation.rb +1 -1
  117. data/lib/rubocop/cop/lint/missing_cop_enable_directive.rb +18 -9
  118. data/lib/rubocop/cop/lint/multiple_comparison.rb +2 -2
  119. data/lib/rubocop/cop/lint/next_without_accumulator.rb +2 -0
  120. data/lib/rubocop/cop/lint/no_return_in_begin_end_blocks.rb +4 -0
  121. data/lib/rubocop/cop/lint/non_deterministic_require_order.rb +4 -2
  122. data/lib/rubocop/cop/lint/number_conversion.rb +6 -6
  123. data/lib/rubocop/cop/lint/numbered_parameter_assignment.rb +1 -1
  124. data/lib/rubocop/cop/lint/parentheses_as_grouped_expression.rb +3 -13
  125. data/lib/rubocop/cop/lint/redundant_cop_disable_directive.rb +23 -9
  126. data/lib/rubocop/cop/lint/redundant_cop_enable_directive.rb +2 -11
  127. data/lib/rubocop/cop/lint/redundant_require_statement.rb +4 -2
  128. data/lib/rubocop/cop/lint/redundant_safe_navigation.rb +23 -6
  129. data/lib/rubocop/cop/lint/redundant_splat_expansion.rb +8 -2
  130. data/lib/rubocop/cop/lint/redundant_type_conversion.rb +3 -3
  131. data/lib/rubocop/cop/lint/require_relative_self_path.rb +3 -1
  132. data/lib/rubocop/cop/lint/rescue_exception.rb +1 -4
  133. data/lib/rubocop/cop/lint/safe_navigation_chain.rb +17 -0
  134. data/lib/rubocop/cop/lint/safe_navigation_consistency.rb +7 -1
  135. data/lib/rubocop/cop/lint/self_assignment.rb +15 -6
  136. data/lib/rubocop/cop/lint/shadowed_argument.rb +7 -7
  137. data/lib/rubocop/cop/lint/shadowed_exception.rb +1 -1
  138. data/lib/rubocop/cop/lint/struct_new_override.rb +17 -1
  139. data/lib/rubocop/cop/lint/syntax.rb +25 -1
  140. data/lib/rubocop/cop/lint/to_json.rb +12 -16
  141. data/lib/rubocop/cop/lint/trailing_comma_in_attribute_declaration.rb +1 -0
  142. data/lib/rubocop/cop/lint/unescaped_bracket_in_regexp.rb +1 -1
  143. data/lib/rubocop/cop/lint/unmodified_reduce_accumulator.rb +1 -0
  144. data/lib/rubocop/cop/lint/unreachable_code.rb +7 -5
  145. data/lib/rubocop/cop/lint/unreachable_pattern_branch.rb +113 -0
  146. data/lib/rubocop/cop/lint/unused_method_argument.rb +10 -0
  147. data/lib/rubocop/cop/lint/uri_escape_unescape.rb +2 -0
  148. data/lib/rubocop/cop/lint/useless_assignment.rb +48 -25
  149. data/lib/rubocop/cop/lint/useless_constant_scoping.rb +4 -4
  150. data/lib/rubocop/cop/lint/useless_default_value_argument.rb +2 -0
  151. data/lib/rubocop/cop/lint/useless_or.rb +15 -2
  152. data/lib/rubocop/cop/lint/useless_ruby2_keywords.rb +1 -1
  153. data/lib/rubocop/cop/lint/utils/nil_receiver_checker.rb +37 -11
  154. data/lib/rubocop/cop/lint/void.rb +39 -12
  155. data/lib/rubocop/cop/message_annotator.rb +1 -1
  156. data/lib/rubocop/cop/metrics/block_length.rb +1 -1
  157. data/lib/rubocop/cop/metrics/block_nesting.rb +23 -0
  158. data/lib/rubocop/cop/metrics/method_length.rb +1 -1
  159. data/lib/rubocop/cop/metrics/utils/abc_size_calculator.rb +4 -3
  160. data/lib/rubocop/cop/metrics/utils/iterating_block.rb +1 -1
  161. data/lib/rubocop/cop/migration/department_name.rb +12 -1
  162. data/lib/rubocop/cop/mixin/check_line_breakable.rb +2 -2
  163. data/lib/rubocop/cop/mixin/check_single_line_suitability.rb +4 -6
  164. data/lib/rubocop/cop/mixin/code_length.rb +1 -1
  165. data/lib/rubocop/cop/mixin/configurable_max.rb +6 -5
  166. data/lib/rubocop/cop/mixin/end_keyword_alignment.rb +1 -7
  167. data/lib/rubocop/cop/mixin/hash_shorthand_syntax.rb +5 -5
  168. data/lib/rubocop/cop/mixin/hash_transform_method/autocorrection.rb +63 -0
  169. data/lib/rubocop/cop/mixin/hash_transform_method.rb +10 -60
  170. data/lib/rubocop/cop/mixin/line_length_help.rb +21 -2
  171. data/lib/rubocop/cop/mixin/method_complexity.rb +1 -1
  172. data/lib/rubocop/cop/mixin/multiline_expression_indentation.rb +1 -1
  173. data/lib/rubocop/cop/mixin/multiline_literal_brace_layout.rb +1 -1
  174. data/lib/rubocop/cop/mixin/project_index_help.rb +48 -0
  175. data/lib/rubocop/cop/mixin/space_after_punctuation.rb +5 -4
  176. data/lib/rubocop/cop/mixin/statement_modifier.rb +0 -6
  177. data/lib/rubocop/cop/mixin/trailing_comma.rb +8 -5
  178. data/lib/rubocop/cop/mixin.rb +86 -0
  179. data/lib/rubocop/cop/naming/binary_operator_parameter_name.rb +1 -1
  180. data/lib/rubocop/cop/naming/block_parameter_name.rb +1 -1
  181. data/lib/rubocop/cop/naming/memoized_instance_variable_name.rb +1 -1
  182. data/lib/rubocop/cop/naming/method_name.rb +5 -3
  183. data/lib/rubocop/cop/naming/predicate_method.rb +32 -8
  184. data/lib/rubocop/cop/naming/predicate_prefix.rb +12 -12
  185. data/lib/rubocop/cop/naming/rescued_exceptions_variable_name.rb +1 -1
  186. data/lib/rubocop/cop/offense.rb +17 -1
  187. data/lib/rubocop/cop/registry.rb +62 -38
  188. data/lib/rubocop/cop/security/eval.rb +15 -2
  189. data/lib/rubocop/cop/security/io_methods.rb +1 -1
  190. data/lib/rubocop/cop/security/json_load.rb +33 -11
  191. data/lib/rubocop/cop/style/access_modifier_declarations.rb +15 -4
  192. data/lib/rubocop/cop/style/accessor_grouping.rb +4 -2
  193. data/lib/rubocop/cop/style/alias.rb +14 -2
  194. data/lib/rubocop/cop/style/and_or.rb +1 -0
  195. data/lib/rubocop/cop/style/arguments_forwarding.rb +25 -7
  196. data/lib/rubocop/cop/style/array_intersect.rb +46 -12
  197. data/lib/rubocop/cop/style/array_intersect_with_single_element.rb +47 -0
  198. data/lib/rubocop/cop/style/array_join.rb +4 -2
  199. data/lib/rubocop/cop/style/ascii_comments.rb +6 -3
  200. data/lib/rubocop/cop/style/attr.rb +5 -2
  201. data/lib/rubocop/cop/style/bare_percent_literals.rb +4 -3
  202. data/lib/rubocop/cop/style/begin_block.rb +3 -1
  203. data/lib/rubocop/cop/style/bitwise_predicate.rb +8 -1
  204. data/lib/rubocop/cop/style/block_delimiters.rb +27 -34
  205. data/lib/rubocop/cop/style/case_equality.rb +15 -13
  206. data/lib/rubocop/cop/style/character_literal.rb +2 -2
  207. data/lib/rubocop/cop/style/class_and_module_children.rb +19 -2
  208. data/lib/rubocop/cop/style/collection_compact.rb +36 -16
  209. data/lib/rubocop/cop/style/colon_method_call.rb +3 -1
  210. data/lib/rubocop/cop/style/concat_array_literals.rb +2 -0
  211. data/lib/rubocop/cop/style/conditional_assignment.rb +8 -18
  212. data/lib/rubocop/cop/style/constant_visibility.rb +17 -12
  213. data/lib/rubocop/cop/style/copyright.rb +22 -11
  214. data/lib/rubocop/cop/style/date_time.rb +2 -2
  215. data/lib/rubocop/cop/style/disable_cops_within_source_code_directive.rb +1 -1
  216. data/lib/rubocop/cop/style/document_dynamic_eval_definition.rb +6 -1
  217. data/lib/rubocop/cop/style/documentation.rb +6 -6
  218. data/lib/rubocop/cop/style/documentation_method.rb +8 -8
  219. data/lib/rubocop/cop/style/double_negation.rb +1 -1
  220. data/lib/rubocop/cop/style/each_for_simple_loop.rb +1 -1
  221. data/lib/rubocop/cop/style/each_with_object.rb +2 -0
  222. data/lib/rubocop/cop/style/empty_block_parameter.rb +1 -1
  223. data/lib/rubocop/cop/style/empty_class_definition.rb +119 -0
  224. data/lib/rubocop/cop/style/empty_lambda_parameter.rb +1 -1
  225. data/lib/rubocop/cop/style/empty_method.rb +0 -6
  226. data/lib/rubocop/cop/style/encoding.rb +7 -1
  227. data/lib/rubocop/cop/style/end_block.rb +3 -1
  228. data/lib/rubocop/cop/style/endless_method.rb +23 -5
  229. data/lib/rubocop/cop/style/explicit_block_argument.rb +1 -1
  230. data/lib/rubocop/cop/style/file_open.rb +84 -0
  231. data/lib/rubocop/cop/style/file_write.rb +18 -16
  232. data/lib/rubocop/cop/style/float_division.rb +15 -1
  233. data/lib/rubocop/cop/style/for.rb +3 -0
  234. data/lib/rubocop/cop/style/format_string.rb +4 -3
  235. data/lib/rubocop/cop/style/format_string_token.rb +49 -5
  236. data/lib/rubocop/cop/style/global_vars.rb +5 -2
  237. data/lib/rubocop/cop/style/guard_clause.rb +27 -22
  238. data/lib/rubocop/cop/style/hash_as_last_array_item.rb +27 -9
  239. data/lib/rubocop/cop/style/hash_conversion.rb +1 -1
  240. data/lib/rubocop/cop/style/hash_lookup_method.rb +106 -0
  241. data/lib/rubocop/cop/style/hash_syntax.rb +1 -1
  242. data/lib/rubocop/cop/style/hash_transform_keys.rb +17 -7
  243. data/lib/rubocop/cop/style/hash_transform_values.rb +17 -7
  244. data/lib/rubocop/cop/style/if_inside_else.rb +16 -7
  245. data/lib/rubocop/cop/style/if_unless_modifier.rb +57 -17
  246. data/lib/rubocop/cop/style/if_unless_modifier_of_if_unless.rb +12 -12
  247. data/lib/rubocop/cop/style/if_with_boolean_literal_branches.rb +4 -1
  248. data/lib/rubocop/cop/style/if_with_semicolon.rb +7 -5
  249. data/lib/rubocop/cop/style/infinite_loop.rb +1 -1
  250. data/lib/rubocop/cop/style/inline_comment.rb +4 -1
  251. data/lib/rubocop/cop/style/ip_addresses.rb +1 -2
  252. data/lib/rubocop/cop/style/lambda_call.rb +8 -8
  253. data/lib/rubocop/cop/style/magic_comment_format.rb +3 -3
  254. data/lib/rubocop/cop/style/map_join.rb +123 -0
  255. data/lib/rubocop/cop/style/method_call_with_args_parentheses/require_parentheses.rb +15 -2
  256. data/lib/rubocop/cop/style/method_call_with_args_parentheses.rb +17 -4
  257. data/lib/rubocop/cop/style/method_def_parentheses.rb +2 -4
  258. data/lib/rubocop/cop/style/min_max_comparison.rb +1 -1
  259. data/lib/rubocop/cop/style/module_member_existence_check.rb +110 -0
  260. data/lib/rubocop/cop/style/multiline_if_then.rb +4 -4
  261. data/lib/rubocop/cop/style/multiline_method_signature.rb +2 -4
  262. data/lib/rubocop/cop/style/mutable_constant.rb +1 -1
  263. data/lib/rubocop/cop/style/negative_array_index.rb +220 -0
  264. data/lib/rubocop/cop/style/nil_comparison.rb +11 -10
  265. data/lib/rubocop/cop/style/nil_lambda.rb +1 -1
  266. data/lib/rubocop/cop/style/non_nil_check.rb +5 -11
  267. data/lib/rubocop/cop/style/not.rb +2 -0
  268. data/lib/rubocop/cop/style/numeric_literals.rb +3 -2
  269. data/lib/rubocop/cop/style/one_class_per_file.rb +115 -0
  270. data/lib/rubocop/cop/style/one_line_conditional.rb +21 -12
  271. data/lib/rubocop/cop/style/operator_method_call.rb +11 -2
  272. data/lib/rubocop/cop/style/parallel_assignment.rb +6 -2
  273. data/lib/rubocop/cop/style/partition_instead_of_double_select.rb +270 -0
  274. data/lib/rubocop/cop/style/percent_literal_delimiters.rb +2 -0
  275. data/lib/rubocop/cop/style/predicate_with_kind.rb +84 -0
  276. data/lib/rubocop/cop/style/preferred_hash_methods.rb +12 -12
  277. data/lib/rubocop/cop/style/proc.rb +3 -2
  278. data/lib/rubocop/cop/style/raise_args.rb +1 -1
  279. data/lib/rubocop/cop/style/reduce_to_hash.rb +200 -0
  280. data/lib/rubocop/cop/style/redundant_argument.rb +2 -0
  281. data/lib/rubocop/cop/style/redundant_array_constructor.rb +2 -2
  282. data/lib/rubocop/cop/style/redundant_begin.rb +37 -3
  283. data/lib/rubocop/cop/style/redundant_condition.rb +6 -3
  284. data/lib/rubocop/cop/style/redundant_constant_base.rb +5 -5
  285. data/lib/rubocop/cop/style/redundant_each.rb +3 -3
  286. data/lib/rubocop/cop/style/redundant_exception.rb +1 -1
  287. data/lib/rubocop/cop/style/redundant_fetch_block.rb +1 -1
  288. data/lib/rubocop/cop/style/redundant_format.rb +26 -5
  289. data/lib/rubocop/cop/style/redundant_interpolation.rb +11 -2
  290. data/lib/rubocop/cop/style/redundant_interpolation_unfreeze.rb +26 -10
  291. data/lib/rubocop/cop/style/redundant_line_continuation.rb +16 -0
  292. data/lib/rubocop/cop/style/redundant_min_max_by.rb +93 -0
  293. data/lib/rubocop/cop/style/redundant_parentheses.rb +36 -30
  294. data/lib/rubocop/cop/style/redundant_percent_q.rb +5 -3
  295. data/lib/rubocop/cop/style/redundant_regexp_argument.rb +9 -0
  296. data/lib/rubocop/cop/style/redundant_regexp_constructor.rb +2 -2
  297. data/lib/rubocop/cop/style/redundant_regexp_escape.rb +8 -0
  298. data/lib/rubocop/cop/style/redundant_return.rb +3 -1
  299. data/lib/rubocop/cop/style/redundant_self.rb +2 -2
  300. data/lib/rubocop/cop/style/redundant_self_assignment_branch.rb +0 -5
  301. data/lib/rubocop/cop/style/redundant_sort.rb +7 -7
  302. data/lib/rubocop/cop/style/redundant_struct_keyword_init.rb +114 -0
  303. data/lib/rubocop/cop/style/regexp_literal.rb +31 -2
  304. data/lib/rubocop/cop/style/rescue_modifier.rb +3 -3
  305. data/lib/rubocop/cop/style/reverse_find.rb +51 -0
  306. data/lib/rubocop/cop/style/safe_navigation.rb +25 -8
  307. data/lib/rubocop/cop/style/select_by_kind.rb +158 -0
  308. data/lib/rubocop/cop/style/select_by_range.rb +197 -0
  309. data/lib/rubocop/cop/style/select_by_regexp.rb +51 -21
  310. data/lib/rubocop/cop/style/self_assignment.rb +1 -1
  311. data/lib/rubocop/cop/style/semicolon.rb +25 -7
  312. data/lib/rubocop/cop/style/single_line_block_params.rb +2 -2
  313. data/lib/rubocop/cop/style/single_line_do_end_block.rb +1 -1
  314. data/lib/rubocop/cop/style/single_line_methods.rb +3 -1
  315. data/lib/rubocop/cop/style/sole_nested_conditional.rb +12 -3
  316. data/lib/rubocop/cop/style/special_global_vars.rb +6 -1
  317. data/lib/rubocop/cop/style/string_concatenation.rb +17 -13
  318. data/lib/rubocop/cop/style/struct_inheritance.rb +13 -0
  319. data/lib/rubocop/cop/style/super_arguments.rb +2 -2
  320. data/lib/rubocop/cop/style/symbol_array.rb +1 -1
  321. data/lib/rubocop/cop/style/symbol_proc.rb +7 -6
  322. data/lib/rubocop/cop/style/tally_method.rb +181 -0
  323. data/lib/rubocop/cop/style/top_level_method_definition.rb +2 -2
  324. data/lib/rubocop/cop/style/trailing_comma_in_arguments.rb +45 -0
  325. data/lib/rubocop/cop/style/trailing_comma_in_block_args.rb +1 -1
  326. data/lib/rubocop/cop/style/trailing_method_end_statement.rb +1 -0
  327. data/lib/rubocop/cop/style/trailing_underscore_variable.rb +11 -11
  328. data/lib/rubocop/cop/style/unless_else.rb +10 -9
  329. data/lib/rubocop/cop/style/unless_logical_operators.rb +3 -3
  330. data/lib/rubocop/cop/style/while_until_modifier.rb +16 -0
  331. data/lib/rubocop/cop/style/yoda_condition.rb +1 -1
  332. data/lib/rubocop/cop/style/yoda_expression.rb +1 -1
  333. data/lib/rubocop/cop/team.rb +87 -36
  334. data/lib/rubocop/cop/util.rb +2 -3
  335. data/lib/rubocop/cop/utils/format_string.rb +10 -0
  336. data/lib/rubocop/cop/variable_force/branch.rb +30 -6
  337. data/lib/rubocop/cop/variable_force/variable.rb +1 -1
  338. data/lib/rubocop/cop/variable_force.rb +9 -7
  339. data/lib/rubocop/cops_documentation_generator.rb +4 -4
  340. data/lib/rubocop/directive_comment.rb +48 -4
  341. data/lib/rubocop/file_patterns.rb +9 -1
  342. data/lib/rubocop/formatter/clang_style_formatter.rb +5 -2
  343. data/lib/rubocop/formatter/disabled_config_formatter.rb +24 -7
  344. data/lib/rubocop/formatter/formatter_set.rb +2 -2
  345. data/lib/rubocop/formatter/junit_formatter.rb +1 -1
  346. data/lib/rubocop/formatter/simple_text_formatter.rb +0 -2
  347. data/lib/rubocop/formatter/tap_formatter.rb +5 -2
  348. data/lib/rubocop/formatter/worst_offenders_formatter.rb +1 -1
  349. data/lib/rubocop/formatter.rb +22 -21
  350. data/lib/rubocop/lsp/diagnostic.rb +18 -33
  351. data/lib/rubocop/lsp/disable_comment_edits.rb +135 -0
  352. data/lib/rubocop/lsp/routes.rb +43 -7
  353. data/lib/rubocop/lsp/runtime.rb +13 -4
  354. data/lib/rubocop/lsp/stdin_runner.rb +8 -17
  355. data/lib/rubocop/magic_comment.rb +20 -0
  356. data/lib/rubocop/mcp/server.rb +200 -0
  357. data/lib/rubocop/options.rb +35 -4
  358. data/lib/rubocop/path_util.rb +14 -2
  359. data/lib/rubocop/plugin/loader.rb +1 -1
  360. data/lib/rubocop/project_index_loader.rb +66 -0
  361. data/lib/rubocop/rake_task.rb +1 -1
  362. data/lib/rubocop/remote_config.rb +10 -8
  363. data/lib/rubocop/result_cache.rb +61 -38
  364. data/lib/rubocop/rspec/cop_helper.rb +8 -0
  365. data/lib/rubocop/rspec/shared_contexts.rb +39 -5
  366. data/lib/rubocop/rspec/support.rb +2 -1
  367. data/lib/rubocop/runner.rb +134 -57
  368. data/lib/rubocop/server/cache.rb +6 -29
  369. data/lib/rubocop/server/core.rb +2 -0
  370. data/lib/rubocop/target_finder.rb +17 -10
  371. data/lib/rubocop/target_ruby.rb +31 -14
  372. data/lib/rubocop/version.rb +21 -3
  373. data/lib/rubocop.rb +28 -96
  374. data/lib/ruby_lsp/rubocop/addon.rb +23 -8
  375. data/lib/ruby_lsp/rubocop/runtime_adapter.rb +49 -15
  376. metadata +38 -9
@@ -3,8 +3,8 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Style
6
- # Checks for the instantiation of regexp using redundant `Regexp.new` or `Regexp.compile`.
7
- # Autocorrect replaces to regexp literal which is the simplest and fastest.
6
+ # Checks for the instantiation of a regexp using a redundant `Regexp.new` or `Regexp.compile`.
7
+ # Autocorrect replaces it with a regexp literal which is the simplest and fastest.
8
8
  #
9
9
  # @example
10
10
  #
@@ -41,6 +41,7 @@ module RuboCop
41
41
  ALLOWED_ALWAYS_ESCAPES = " \n[]^\\#".chars.freeze
42
42
  ALLOWED_WITHIN_CHAR_CLASS_METACHAR_ESCAPES = '-'.chars.freeze
43
43
  ALLOWED_OUTSIDE_CHAR_CLASS_METACHAR_ESCAPES = '.*+?{}()|$'.chars.freeze
44
+ INTERPOLATION_SIGILS = %w[@ $].freeze
44
45
 
45
46
  def on_regexp(node)
46
47
  each_escape(node) do |char, index, within_character_class|
@@ -64,6 +65,7 @@ module RuboCop
64
65
  # different versions of Ruby so that e.g. /\i/ != /i/
65
66
  return true if /[[:alnum:]]/.match?(char)
66
67
  return true if ALLOWED_ALWAYS_ESCAPES.include?(char) || delimiter?(node, char)
68
+ return true if requires_escape_to_avoid_interpolation?(node.source[index], char)
67
69
 
68
70
  if within_character_class
69
71
  ALLOWED_WITHIN_CHAR_CLASS_METACHAR_ESCAPES.include?(char) &&
@@ -95,6 +97,12 @@ module RuboCop
95
97
  delimiters.include?(char)
96
98
  end
97
99
 
100
+ def requires_escape_to_avoid_interpolation?(char_before_escape, escaped_char)
101
+ # Preserve escapes after '#' that would otherwise trigger interpolation:
102
+ # '#@ivar', '#@@cvar', and '#$gvar'.
103
+ char_before_escape == '#' && INTERPOLATION_SIGILS.include?(escaped_char)
104
+ end
105
+
98
106
  def each_escape(node)
99
107
  node.parsed_tree&.traverse&.reduce(0) do |char_class_depth, (event, expr)|
100
108
  yield(expr.text[1], expr.ts, !char_class_depth.zero?) if expr.type == :escape
@@ -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
@@ -59,7 +59,7 @@ module RuboCop
59
59
 
60
60
  def initialize(config = nil, options = nil)
61
61
  super
62
- @allowed_send_nodes = []
62
+ @allowed_send_nodes = Set.new.compare_by_identity
63
63
  @local_variables_scopes = Hash.new { |hash, key| hash[key] = [] }.compare_by_identity
64
64
  end
65
65
 
@@ -187,7 +187,7 @@ module RuboCop
187
187
  def allow_self(node)
188
188
  return unless node.send_type? && node.self_receiver?
189
189
 
190
- @allowed_send_nodes << node
190
+ @allowed_send_nodes.add(node)
191
191
  end
192
192
 
193
193
  def add_lhs_to_local_variables_scopes(rhs, lhs)
@@ -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
 
@@ -26,17 +26,17 @@ module RuboCop
26
26
  #
27
27
  # [source,ruby]
28
28
  # ----
29
- # class MyString < String; end
30
- # strings = [MyString.new('test'), 'test']
31
- # strings.sort.last.class #=> String
32
- # strings.max.class #=> MyString
29
+ # class MyString < String; end
30
+ # strings = [MyString.new('test'), 'test']
31
+ # strings.sort.last.class #=> String
32
+ # strings.max.class #=> MyString
33
33
  # ----
34
34
  #
35
35
  # [source,ruby]
36
36
  # ----
37
- # words = %w(dog horse mouse)
38
- # words.sort_by { |word| word.length }.last #=> 'mouse'
39
- # words.max_by { |word| word.length } #=> 'horse'
37
+ # words = %w(dog horse mouse)
38
+ # words.sort_by { |word| word.length }.last #=> 'mouse'
39
+ # words.max_by { |word| word.length } #=> 'horse'
40
40
  # ----
41
41
  #
42
42
  # @example
@@ -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
@@ -5,8 +5,8 @@ module RuboCop
5
5
  module Style
6
6
  # Enforces using `//` or `%r` around regular expressions.
7
7
  #
8
- # NOTE: The following `%r` cases using a regexp starts with a blank or `=`
9
- # as a method argument allowed to prevent syntax errors.
8
+ # NOTE: The following `%r` cases using a regexp that starts with a blank or `=`
9
+ # as a method argument are allowed to prevent syntax errors.
10
10
  #
11
11
  # [source,ruby]
12
12
  # ----
@@ -98,7 +98,16 @@ module RuboCop
98
98
  MSG_USE_SLASHES = 'Use `//` around regular expression.'
99
99
  MSG_USE_PERCENT_R = 'Use `%r` around regular expression.'
100
100
 
101
+ PAIR_DELIMITER_PATTERNS = {
102
+ ['(', ')'] => /\\.|[()]/,
103
+ ['[', ']'] => /\\.|[\[\]]/,
104
+ ['{', '}'] => /\\.|[{}]/,
105
+ ['<', '>'] => /\\.|[<>]/
106
+ }.freeze
107
+
101
108
  def on_regexp(node)
109
+ return if slash_literal?(node) && percent_r_delimiters_conflict?(node)
110
+
102
111
  message = if slash_literal?(node)
103
112
  MSG_USE_PERCENT_R unless allowed_slash_literal?(node)
104
113
  else
@@ -115,6 +124,26 @@ module RuboCop
115
124
 
116
125
  private
117
126
 
127
+ def percent_r_delimiters_conflict?(node)
128
+ opening, closing = preferred_delimiters
129
+ return false unless (pattern = PAIR_DELIMITER_PATTERNS[[opening, closing]])
130
+
131
+ !balanced_delimiters?(node_body(node), opening, closing, pattern)
132
+ end
133
+
134
+ def balanced_delimiters?(text, opening, closing, pattern)
135
+ depth = 0
136
+ text.scan(pattern) do |match|
137
+ if match == opening
138
+ depth += 1
139
+ elsif match == closing
140
+ depth -= 1
141
+ return false if depth.negative?
142
+ end
143
+ end
144
+ depth.zero?
145
+ end
146
+
118
147
  def allowed_slash_literal?(node)
119
148
  (style == :slashes && !contains_disallowed_slash?(node)) || allowed_mixed_slash?(node)
120
149
  end
@@ -3,17 +3,17 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Style
6
- # Checks for uses of `rescue` in its modifier form is added for following
6
+ # Checks for uses of `rescue` in its modifier form. It is added for the following
7
7
  # reasons:
8
8
  #
9
9
  # * The syntax of modifier form `rescue` can be misleading because it
10
10
  # might lead us to believe that `rescue` handles the given exception
11
- # but it actually rescue all exceptions to return the given rescue
11
+ # but it actually rescues all exceptions to return the given rescue
12
12
  # block. In this case, value returned by handle_error or
13
13
  # SomeException.
14
14
  #
15
15
  # * Modifier form `rescue` would rescue all the exceptions. It would
16
- # silently skip all exception or errors and handle the error.
16
+ # silently skip all exceptions or errors and handle the error.
17
17
  # Example: If `NoMethodError` is raised, modifier form rescue would
18
18
  # handle the exception.
19
19
  #
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Style
6
+ # Identifies places where `array.reverse.find` can be replaced by `array.rfind`.
7
+ #
8
+ # @safety
9
+ # This cop is unsafe because it cannot be guaranteed that the receiver
10
+ # is an `Array` or responds to the replacement method.
11
+ #
12
+ # @example
13
+ # # bad
14
+ # array.reverse.find { |item| item.even? }
15
+ # array.reverse.detect { |item| item.even? }
16
+ # array.reverse_each.find { |item| item.even? }
17
+ # array.reverse_each.detect { |item| item.even? }
18
+ #
19
+ # # good
20
+ # array.rfind { |item| item.even? }
21
+ #
22
+ class ReverseFind < Base
23
+ extend AutoCorrector
24
+ extend TargetRubyVersion
25
+
26
+ MSG = 'Use `rfind` instead.'
27
+ RESTRICT_ON_SEND = %i[find detect].freeze
28
+
29
+ minimum_target_ruby_version 4.0
30
+
31
+ # @!method reverse_find?(node)
32
+ def_node_matcher :reverse_find?, <<~PATTERN
33
+ (call
34
+ (call
35
+ _ {:reverse :reverse_each}) {:find :detect} (block_pass sym)?)
36
+ PATTERN
37
+
38
+ def on_send(node)
39
+ return unless reverse_find?(node)
40
+
41
+ range = node.children.first.loc.selector.join(node.loc.selector)
42
+
43
+ add_offense(range) do |corrector|
44
+ corrector.replace(range, 'rfind')
45
+ end
46
+ end
47
+ alias on_csend on_send
48
+ end
49
+ end
50
+ end
51
+ end
@@ -142,6 +142,7 @@ module RuboCop
142
142
  # @!method strip_begin(node)
143
143
  def_node_matcher :strip_begin, '{ (begin $!begin) $!(begin) }'
144
144
 
145
+ # rubocop:disable Metrics/AbcSize
145
146
  def on_if(node)
146
147
  return if allowed_if_condition?(node)
147
148
 
@@ -155,9 +156,11 @@ module RuboCop
155
156
  removal_ranges = [begin_range(node, body), end_range(node, body)]
156
157
 
157
158
  report_offense(node, method_chain, method_call, *removal_ranges) do |corrector|
159
+ corrector.replace(receiver, checked_variable.source) if checked_variable.csend_type?
158
160
  corrector.insert_before(method_call.loc.dot, '&') unless method_call.safe_navigation?
159
161
  end
160
162
  end
163
+ # rubocop:enable Metrics/AbcSize
161
164
 
162
165
  def on_and(node) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength
163
166
  collect_and_clauses(node).each do |(lhs, lhs_operator_range), (rhs, _rhs_operator_range)|
@@ -259,8 +262,14 @@ module RuboCop
259
262
  end
260
263
 
261
264
  def dotless_operator_call?(method_call)
265
+ return true if dotless_operator_method?(method_call)
266
+
262
267
  method_call = method_call.parent while method_call.parent.send_type?
263
268
 
269
+ dotless_operator_method?(method_call)
270
+ end
271
+
272
+ def dotless_operator_method?(method_call)
264
273
  return false if method_call.loc.dot
265
274
 
266
275
  method_call.method?(:[]) || method_call.method?(:[]=) || method_call.operator_method?
@@ -335,8 +344,16 @@ module RuboCop
335
344
 
336
345
  def matching_call_nodes?(left, right)
337
346
  return false unless left && right.respond_to?(:call_type?)
347
+ return false unless left.call_type? && right.call_type?
338
348
 
339
- left.call_type? && right.call_type? && left.children == right.children
349
+ # Compare receiver and method name, but ignore the difference between
350
+ # safe navigation method call (`&.`) and dot method call (`.`).
351
+ left_receiver, left_method, *left_args = left.children
352
+ right_receiver, right_method, *right_args = right.children
353
+
354
+ left_method == right_method &&
355
+ matching_nodes?(left_receiver, right_receiver) &&
356
+ left_args == right_args
340
357
  end
341
358
 
342
359
  def chain_length(method_chain, method)
@@ -350,13 +367,13 @@ module RuboCop
350
367
  def unsafe_method_used?(node, method_chain, method)
351
368
  return true if unsafe_method?(node, method)
352
369
 
353
- method.each_ancestor(:send).any? do |ancestor|
354
- break true unless config.cop_enabled?('Lint/SafeNavigationChain')
355
-
356
- break true if unsafe_method?(node, ancestor)
357
- break true if nil_methods.include?(ancestor.method_name)
358
- 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
359
375
  end
376
+ false
360
377
  end
361
378
 
362
379
  def unsafe_method?(node, send_node)
@@ -392,7 +409,7 @@ module RuboCop
392
409
  start_method,
393
410
  method_chain)
394
411
  start_method.each_ancestor do |ancestor|
395
- break unless %i[send block].include?(ancestor.type)
412
+ break unless ancestor.type?(:call, :any_block)
396
413
  next if !ancestor.send_type? || ancestor.operator_method?
397
414
 
398
415
  corrector.insert_before(ancestor.loc.dot, '&')
@@ -0,0 +1,158 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Style
6
+ # Looks for places where a subset of an Enumerable (array,
7
+ # range, set, etc.; see note below) is calculated based on a class type
8
+ # check, and suggests `grep` or `grep_v` instead.
9
+ #
10
+ # NOTE: Hashes do not behave as you may expect with `grep`, which
11
+ # means that `hash.grep` is not equivalent to `hash.select`. Although
12
+ # RuboCop is limited by static analysis, this cop attempts to avoid
13
+ # registering an offense when the receiver is a hash (hash literal,
14
+ # `Hash.new`, `Hash#[]`, or `to_h`/`to_hash`).
15
+ #
16
+ # @safety
17
+ # Autocorrection is marked as unsafe because the cop cannot guarantee
18
+ # that the receiver is actually an array by static analysis, so the
19
+ # correction may not be actually equivalent.
20
+ #
21
+ # @example
22
+ # # bad (select or find_all)
23
+ # array.select { |x| x.is_a?(Foo) }
24
+ # array.select { |x| x.kind_of?(Foo) }
25
+ #
26
+ # # bad (reject)
27
+ # array.reject { |x| x.is_a?(Foo) }
28
+ #
29
+ # # bad (negative form)
30
+ # array.reject { |x| !x.is_a?(Foo) }
31
+ #
32
+ # # good
33
+ # array.grep(Foo)
34
+ # array.grep_v(Foo)
35
+ class SelectByKind < Base
36
+ extend AutoCorrector
37
+ include RangeHelp
38
+
39
+ MSG = 'Prefer `%<replacement>s` to `%<original_method>s` with a kind check.'
40
+ RESTRICT_ON_SEND = %i[select filter find_all reject].freeze
41
+ SELECT_METHODS = %i[select filter find_all].freeze
42
+ CLASS_CHECK_METHODS = %i[is_a? kind_of?].to_set.freeze
43
+
44
+ # @!method class_check?(node)
45
+ def_node_matcher :class_check?, <<~PATTERN
46
+ {
47
+ (block call (args (arg $_)) ${(send (lvar _) %CLASS_CHECK_METHODS _)})
48
+ (block call (args (arg $_)) ${(send (send (lvar _) %CLASS_CHECK_METHODS _) :!)})
49
+ (numblock call $1 ${(send (lvar _) %CLASS_CHECK_METHODS _)})
50
+ (numblock call $1 ${(send (send (lvar _) %CLASS_CHECK_METHODS _) :!)})
51
+ (itblock call $_ ${(send (lvar _) %CLASS_CHECK_METHODS _)})
52
+ (itblock call $_ ${(send (send (lvar _) %CLASS_CHECK_METHODS _) :!)})
53
+ }
54
+ PATTERN
55
+
56
+ # Returns true if a node appears to return a hash
57
+ # @!method creates_hash?(node)
58
+ def_node_matcher :creates_hash?, <<~PATTERN
59
+ {
60
+ (call (const _ :Hash) {:new :[]} ...)
61
+ (block (call (const _ :Hash) :new ...) ...)
62
+ (call _ { :to_h :to_hash } ...)
63
+ }
64
+ PATTERN
65
+
66
+ # @!method env_const?(node)
67
+ def_node_matcher :env_const?, <<~PATTERN
68
+ (const {nil? cbase} :ENV)
69
+ PATTERN
70
+
71
+ # @!method calls_lvar?(node, name)
72
+ def_node_matcher :calls_lvar?, <<~PATTERN
73
+ (send (lvar %1) %CLASS_CHECK_METHODS _)
74
+ PATTERN
75
+
76
+ # @!method negated_calls_lvar?(node, name)
77
+ def_node_matcher :negated_calls_lvar?, <<~PATTERN
78
+ (send (send (lvar %1) %CLASS_CHECK_METHODS _) :!)
79
+ PATTERN
80
+
81
+ def on_send(node)
82
+ return unless (block_node = node.block_node)
83
+ return if block_node.body&.begin_type?
84
+ return if receiver_allowed?(block_node.receiver)
85
+ return unless (class_check_send_node = extract_send_node(block_node))
86
+
87
+ replacement = replacement(class_check_send_node, node)
88
+ class_constant = find_class_constant(class_check_send_node)
89
+
90
+ register_offense(node, block_node, class_constant, replacement)
91
+ end
92
+ alias on_csend on_send
93
+
94
+ private
95
+
96
+ def receiver_allowed?(node)
97
+ return false unless node
98
+
99
+ node.hash_type? || creates_hash?(node) || env_const?(node)
100
+ end
101
+
102
+ def replacement(class_check_send_node, node)
103
+ negated = negated?(class_check_send_node)
104
+
105
+ method_name = node.method_name
106
+
107
+ if SELECT_METHODS.include?(method_name)
108
+ negated ? 'grep_v' : 'grep'
109
+ else # reject
110
+ negated ? 'grep' : 'grep_v'
111
+ end
112
+ end
113
+
114
+ def register_offense(node, block_node, class_constant, replacement)
115
+ message = format(MSG, replacement: replacement, original_method: node.method_name)
116
+
117
+ add_offense(block_node, message: message) do |corrector|
118
+ if class_constant
119
+ range = range_between(node.loc.selector.begin_pos, block_node.loc.end.end_pos)
120
+ corrector.replace(range, "#{replacement}(#{class_constant.source})")
121
+ end
122
+ end
123
+ end
124
+
125
+ def extract_send_node(block_node)
126
+ return unless (block_arg_name, class_check_send_node = class_check?(block_node))
127
+
128
+ block_arg_name = :"_#{block_arg_name}" if block_node.numblock_type?
129
+ block_arg_name = :it if block_node.type?(:itblock)
130
+
131
+ inner_node = unwrap_negation(class_check_send_node)
132
+
133
+ if calls_lvar?(inner_node, block_arg_name) ||
134
+ negated_calls_lvar?(class_check_send_node, block_arg_name)
135
+ class_check_send_node
136
+ end
137
+ end
138
+
139
+ def negated?(class_check_send_node)
140
+ class_check_send_node.send_type? && class_check_send_node.method?(:!)
141
+ end
142
+
143
+ def unwrap_negation(node)
144
+ if node.send_type? && node.method?(:!)
145
+ node.receiver
146
+ else
147
+ node
148
+ end
149
+ end
150
+
151
+ def find_class_constant(node)
152
+ inner_node = unwrap_negation(node)
153
+ inner_node.first_argument if inner_node.send_type?
154
+ end
155
+ end
156
+ end
157
+ end
158
+ end