rubocop 1.80.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 (310) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE.txt +1 -1
  3. data/README.md +2 -2
  4. data/config/default.yml +170 -19
  5. data/config/obsoletion.yml +9 -0
  6. data/lib/rubocop/cache_config.rb +29 -0
  7. data/lib/rubocop/cli/command/auto_generate_config.rb +3 -3
  8. data/lib/rubocop/cli/command/lsp.rb +1 -1
  9. data/lib/rubocop/cli/command/mcp.rb +19 -0
  10. data/lib/rubocop/cli/command/show_cops.rb +2 -2
  11. data/lib/rubocop/cli/command/show_docs_url.rb +1 -1
  12. data/lib/rubocop/cli.rb +28 -6
  13. data/lib/rubocop/comment_config.rb +62 -17
  14. data/lib/rubocop/config.rb +14 -10
  15. data/lib/rubocop/config_finder.rb +1 -1
  16. data/lib/rubocop/config_loader.rb +20 -21
  17. data/lib/rubocop/config_loader_resolver.rb +9 -7
  18. data/lib/rubocop/config_obsoletion/extracted_cop.rb +4 -2
  19. data/lib/rubocop/config_store.rb +6 -1
  20. data/lib/rubocop/config_validator.rb +1 -1
  21. data/lib/rubocop/cop/autocorrect_logic.rb +8 -4
  22. data/lib/rubocop/cop/bundler/gem_version.rb +28 -28
  23. data/lib/rubocop/cop/bundler/ordered_gems.rb +1 -2
  24. data/lib/rubocop/cop/correctors/alignment_corrector.rb +22 -6
  25. data/lib/rubocop/cop/correctors/condition_corrector.rb +1 -1
  26. data/lib/rubocop/cop/correctors/percent_literal_corrector.rb +2 -2
  27. data/lib/rubocop/cop/documentation.rb +2 -3
  28. data/lib/rubocop/cop/gemspec/ordered_dependencies.rb +1 -2
  29. data/lib/rubocop/cop/gemspec/require_mfa.rb +1 -1
  30. data/lib/rubocop/cop/gemspec/ruby_version_globals_usage.rb +10 -5
  31. data/lib/rubocop/cop/internal_affairs/example_heredoc_delimiter.rb +8 -8
  32. data/lib/rubocop/cop/internal_affairs/itblock_handler.rb +69 -0
  33. data/lib/rubocop/cop/internal_affairs/location_exists.rb +28 -2
  34. data/lib/rubocop/cop/internal_affairs/node_matcher_directive.rb +9 -9
  35. data/lib/rubocop/cop/internal_affairs/node_pattern_groups/ast_processor.rb +1 -1
  36. data/lib/rubocop/cop/internal_affairs/node_pattern_groups.rb +3 -1
  37. data/lib/rubocop/cop/internal_affairs/on_send_without_on_csend.rb +1 -1
  38. data/lib/rubocop/cop/internal_affairs/useless_message_assertion.rb +4 -4
  39. data/lib/rubocop/cop/internal_affairs.rb +1 -0
  40. data/lib/rubocop/cop/layout/argument_alignment.rb +2 -2
  41. data/lib/rubocop/cop/layout/array_alignment.rb +1 -1
  42. data/lib/rubocop/cop/layout/case_indentation.rb +3 -1
  43. data/lib/rubocop/cop/layout/class_structure.rb +13 -6
  44. data/lib/rubocop/cop/layout/dot_position.rb +2 -2
  45. data/lib/rubocop/cop/layout/empty_line_after_guard_clause.rb +12 -2
  46. data/lib/rubocop/cop/layout/empty_line_between_defs.rb +31 -13
  47. data/lib/rubocop/cop/layout/empty_lines_after_module_inclusion.rb +1 -1
  48. data/lib/rubocop/cop/layout/empty_lines_around_attribute_accessor.rb +1 -0
  49. data/lib/rubocop/cop/layout/empty_lines_around_block_body.rb +12 -2
  50. data/lib/rubocop/cop/layout/empty_lines_around_class_body.rb +16 -2
  51. data/lib/rubocop/cop/layout/empty_lines_around_module_body.rb +16 -2
  52. data/lib/rubocop/cop/layout/end_alignment.rb +8 -1
  53. data/lib/rubocop/cop/layout/first_argument_indentation.rb +34 -1
  54. data/lib/rubocop/cop/layout/first_array_element_line_break.rb +26 -0
  55. data/lib/rubocop/cop/layout/first_hash_element_indentation.rb +7 -1
  56. data/lib/rubocop/cop/layout/first_hash_element_line_break.rb +25 -25
  57. data/lib/rubocop/cop/layout/hash_alignment.rb +3 -6
  58. data/lib/rubocop/cop/layout/heredoc_argument_closing_parenthesis.rb +2 -2
  59. data/lib/rubocop/cop/layout/heredoc_indentation.rb +33 -3
  60. data/lib/rubocop/cop/layout/indentation_style.rb +1 -1
  61. data/lib/rubocop/cop/layout/indentation_width.rb +111 -7
  62. data/lib/rubocop/cop/layout/line_continuation_spacing.rb +1 -1
  63. data/lib/rubocop/cop/layout/line_length.rb +26 -9
  64. data/lib/rubocop/cop/layout/multiline_array_brace_layout.rb +57 -57
  65. data/lib/rubocop/cop/layout/multiline_assignment_layout.rb +9 -2
  66. data/lib/rubocop/cop/layout/multiline_block_layout.rb +2 -0
  67. data/lib/rubocop/cop/layout/multiline_hash_brace_layout.rb +56 -56
  68. data/lib/rubocop/cop/layout/multiline_method_call_indentation.rb +204 -39
  69. data/lib/rubocop/cop/layout/multiline_operation_indentation.rb +6 -4
  70. data/lib/rubocop/cop/layout/parameter_alignment.rb +1 -1
  71. data/lib/rubocop/cop/layout/redundant_line_break.rb +1 -1
  72. data/lib/rubocop/cop/layout/rescue_ensure_alignment.rb +13 -3
  73. data/lib/rubocop/cop/layout/space_after_comma.rb +2 -10
  74. data/lib/rubocop/cop/layout/space_after_semicolon.rb +1 -1
  75. data/lib/rubocop/cop/layout/space_around_block_parameters.rb +1 -1
  76. data/lib/rubocop/cop/layout/space_around_keyword.rb +4 -2
  77. data/lib/rubocop/cop/layout/space_in_lambda_literal.rb +9 -8
  78. data/lib/rubocop/cop/layout/trailing_whitespace.rb +1 -1
  79. data/lib/rubocop/cop/lint/circular_argument_reference.rb +47 -3
  80. data/lib/rubocop/cop/lint/constant_overwritten_in_rescue.rb +3 -2
  81. data/lib/rubocop/cop/lint/constant_reassignment.rb +59 -9
  82. data/lib/rubocop/cop/lint/constant_resolution.rb +1 -1
  83. data/lib/rubocop/cop/lint/cop_directive_syntax.rb +14 -8
  84. data/lib/rubocop/cop/lint/data_define_override.rb +63 -0
  85. data/lib/rubocop/cop/lint/debugger.rb +0 -2
  86. data/lib/rubocop/cop/lint/deprecated_open_ssl_constant.rb +4 -1
  87. data/lib/rubocop/cop/lint/duplicate_match_pattern.rb +4 -4
  88. data/lib/rubocop/cop/lint/duplicate_methods.rb +111 -12
  89. data/lib/rubocop/cop/lint/else_layout.rb +19 -0
  90. data/lib/rubocop/cop/lint/empty_block.rb +1 -1
  91. data/lib/rubocop/cop/lint/empty_conditional_body.rb +6 -1
  92. data/lib/rubocop/cop/lint/empty_in_pattern.rb +8 -1
  93. data/lib/rubocop/cop/lint/empty_interpolation.rb +11 -0
  94. data/lib/rubocop/cop/lint/empty_when.rb +8 -1
  95. data/lib/rubocop/cop/lint/float_comparison.rb +1 -1
  96. data/lib/rubocop/cop/lint/interpolation_check.rb +7 -2
  97. data/lib/rubocop/cop/lint/literal_as_condition.rb +5 -1
  98. data/lib/rubocop/cop/lint/literal_in_interpolation.rb +1 -1
  99. data/lib/rubocop/cop/lint/missing_cop_enable_directive.rb +16 -6
  100. data/lib/rubocop/cop/lint/next_without_accumulator.rb +2 -0
  101. data/lib/rubocop/cop/lint/no_return_in_begin_end_blocks.rb +4 -0
  102. data/lib/rubocop/cop/lint/non_deterministic_require_order.rb +3 -1
  103. data/lib/rubocop/cop/lint/number_conversion.rb +1 -1
  104. data/lib/rubocop/cop/lint/redundant_cop_disable_directive.rb +23 -9
  105. data/lib/rubocop/cop/lint/redundant_cop_enable_directive.rb +0 -9
  106. data/lib/rubocop/cop/lint/redundant_require_statement.rb +4 -2
  107. data/lib/rubocop/cop/lint/redundant_safe_navigation.rb +23 -6
  108. data/lib/rubocop/cop/lint/redundant_splat_expansion.rb +8 -2
  109. data/lib/rubocop/cop/lint/rescue_exception.rb +1 -4
  110. data/lib/rubocop/cop/lint/safe_navigation_chain.rb +17 -0
  111. data/lib/rubocop/cop/lint/safe_navigation_consistency.rb +7 -1
  112. data/lib/rubocop/cop/lint/self_assignment.rb +10 -2
  113. data/lib/rubocop/cop/lint/shadowed_argument.rb +7 -7
  114. data/lib/rubocop/cop/lint/struct_new_override.rb +17 -1
  115. data/lib/rubocop/cop/lint/syntax.rb +25 -1
  116. data/lib/rubocop/cop/lint/to_json.rb +12 -16
  117. data/lib/rubocop/cop/lint/trailing_comma_in_attribute_declaration.rb +1 -0
  118. data/lib/rubocop/cop/lint/unmodified_reduce_accumulator.rb +1 -0
  119. data/lib/rubocop/cop/lint/unreachable_code.rb +5 -3
  120. data/lib/rubocop/cop/lint/unreachable_pattern_branch.rb +113 -0
  121. data/lib/rubocop/cop/lint/unused_method_argument.rb +10 -0
  122. data/lib/rubocop/cop/lint/useless_assignment.rb +45 -17
  123. data/lib/rubocop/cop/lint/useless_constant_scoping.rb +4 -4
  124. data/lib/rubocop/cop/lint/useless_default_value_argument.rb +2 -0
  125. data/lib/rubocop/cop/lint/useless_or.rb +15 -2
  126. data/lib/rubocop/cop/lint/utils/nil_receiver_checker.rb +24 -9
  127. data/lib/rubocop/cop/lint/void.rb +39 -12
  128. data/lib/rubocop/cop/message_annotator.rb +1 -1
  129. data/lib/rubocop/cop/metrics/block_nesting.rb +23 -0
  130. data/lib/rubocop/cop/metrics/utils/abc_size_calculator.rb +4 -3
  131. data/lib/rubocop/cop/migration/department_name.rb +12 -1
  132. data/lib/rubocop/cop/mixin/check_line_breakable.rb +2 -2
  133. data/lib/rubocop/cop/mixin/check_single_line_suitability.rb +4 -6
  134. data/lib/rubocop/cop/mixin/code_length.rb +1 -1
  135. data/lib/rubocop/cop/mixin/hash_shorthand_syntax.rb +5 -5
  136. data/lib/rubocop/cop/mixin/hash_transform_method/autocorrection.rb +63 -0
  137. data/lib/rubocop/cop/mixin/hash_transform_method.rb +10 -60
  138. data/lib/rubocop/cop/mixin/line_length_help.rb +21 -2
  139. data/lib/rubocop/cop/mixin/method_complexity.rb +1 -1
  140. data/lib/rubocop/cop/mixin/multiline_expression_indentation.rb +1 -1
  141. data/lib/rubocop/cop/mixin/multiline_literal_brace_layout.rb +1 -1
  142. data/lib/rubocop/cop/mixin/space_after_punctuation.rb +5 -4
  143. data/lib/rubocop/cop/mixin/statement_modifier.rb +0 -6
  144. data/lib/rubocop/cop/mixin/trailing_comma.rb +8 -5
  145. data/lib/rubocop/cop/naming/block_parameter_name.rb +1 -1
  146. data/lib/rubocop/cop/naming/method_name.rb +4 -2
  147. data/lib/rubocop/cop/naming/predicate_method.rb +27 -4
  148. data/lib/rubocop/cop/naming/predicate_prefix.rb +11 -11
  149. data/lib/rubocop/cop/offense.rb +9 -1
  150. data/lib/rubocop/cop/registry.rb +20 -13
  151. data/lib/rubocop/cop/security/eval.rb +15 -2
  152. data/lib/rubocop/cop/security/json_load.rb +33 -11
  153. data/lib/rubocop/cop/style/access_modifier_declarations.rb +15 -4
  154. data/lib/rubocop/cop/style/accessor_grouping.rb +4 -2
  155. data/lib/rubocop/cop/style/alias.rb +4 -1
  156. data/lib/rubocop/cop/style/and_or.rb +1 -0
  157. data/lib/rubocop/cop/style/arguments_forwarding.rb +25 -7
  158. data/lib/rubocop/cop/style/array_intersect.rb +2 -2
  159. data/lib/rubocop/cop/style/array_intersect_with_single_element.rb +47 -0
  160. data/lib/rubocop/cop/style/array_join.rb +4 -2
  161. data/lib/rubocop/cop/style/ascii_comments.rb +6 -3
  162. data/lib/rubocop/cop/style/attr.rb +5 -2
  163. data/lib/rubocop/cop/style/bare_percent_literals.rb +4 -3
  164. data/lib/rubocop/cop/style/begin_block.rb +3 -1
  165. data/lib/rubocop/cop/style/block_delimiters.rb +27 -34
  166. data/lib/rubocop/cop/style/case_equality.rb +15 -13
  167. data/lib/rubocop/cop/style/class_and_module_children.rb +11 -2
  168. data/lib/rubocop/cop/style/collection_compact.rb +36 -16
  169. data/lib/rubocop/cop/style/colon_method_call.rb +3 -1
  170. data/lib/rubocop/cop/style/concat_array_literals.rb +2 -0
  171. data/lib/rubocop/cop/style/conditional_assignment.rb +8 -18
  172. data/lib/rubocop/cop/style/constant_visibility.rb +17 -12
  173. data/lib/rubocop/cop/style/copyright.rb +1 -1
  174. data/lib/rubocop/cop/style/documentation.rb +6 -6
  175. data/lib/rubocop/cop/style/documentation_method.rb +8 -8
  176. data/lib/rubocop/cop/style/double_negation.rb +1 -1
  177. data/lib/rubocop/cop/style/each_for_simple_loop.rb +1 -1
  178. data/lib/rubocop/cop/style/each_with_object.rb +2 -0
  179. data/lib/rubocop/cop/style/empty_block_parameter.rb +1 -1
  180. data/lib/rubocop/cop/style/empty_class_definition.rb +119 -0
  181. data/lib/rubocop/cop/style/empty_lambda_parameter.rb +1 -1
  182. data/lib/rubocop/cop/style/empty_method.rb +0 -6
  183. data/lib/rubocop/cop/style/encoding.rb +7 -1
  184. data/lib/rubocop/cop/style/end_block.rb +3 -1
  185. data/lib/rubocop/cop/style/endless_method.rb +23 -5
  186. data/lib/rubocop/cop/style/explicit_block_argument.rb +1 -1
  187. data/lib/rubocop/cop/style/file_open.rb +84 -0
  188. data/lib/rubocop/cop/style/float_division.rb +15 -1
  189. data/lib/rubocop/cop/style/for.rb +3 -0
  190. data/lib/rubocop/cop/style/format_string_token.rb +49 -5
  191. data/lib/rubocop/cop/style/global_vars.rb +5 -2
  192. data/lib/rubocop/cop/style/guard_clause.rb +27 -22
  193. data/lib/rubocop/cop/style/hash_as_last_array_item.rb +27 -9
  194. data/lib/rubocop/cop/style/hash_lookup_method.rb +101 -0
  195. data/lib/rubocop/cop/style/hash_syntax.rb +1 -1
  196. data/lib/rubocop/cop/style/hash_transform_keys.rb +17 -7
  197. data/lib/rubocop/cop/style/hash_transform_values.rb +17 -7
  198. data/lib/rubocop/cop/style/if_inside_else.rb +1 -5
  199. data/lib/rubocop/cop/style/if_unless_modifier.rb +57 -17
  200. data/lib/rubocop/cop/style/if_unless_modifier_of_if_unless.rb +12 -12
  201. data/lib/rubocop/cop/style/if_with_boolean_literal_branches.rb +4 -1
  202. data/lib/rubocop/cop/style/if_with_semicolon.rb +7 -5
  203. data/lib/rubocop/cop/style/inline_comment.rb +4 -1
  204. data/lib/rubocop/cop/style/ip_addresses.rb +1 -2
  205. data/lib/rubocop/cop/style/lambda_call.rb +8 -8
  206. data/lib/rubocop/cop/style/magic_comment_format.rb +2 -2
  207. data/lib/rubocop/cop/style/map_join.rb +123 -0
  208. data/lib/rubocop/cop/style/method_call_with_args_parentheses/require_parentheses.rb +15 -2
  209. data/lib/rubocop/cop/style/method_call_with_args_parentheses.rb +17 -4
  210. data/lib/rubocop/cop/style/method_def_parentheses.rb +2 -4
  211. data/lib/rubocop/cop/style/module_member_existence_check.rb +107 -0
  212. data/lib/rubocop/cop/style/multiline_if_then.rb +4 -4
  213. data/lib/rubocop/cop/style/multiline_method_signature.rb +2 -4
  214. data/lib/rubocop/cop/style/mutable_constant.rb +1 -1
  215. data/lib/rubocop/cop/style/negative_array_index.rb +220 -0
  216. data/lib/rubocop/cop/style/nil_comparison.rb +11 -10
  217. data/lib/rubocop/cop/style/nil_lambda.rb +1 -1
  218. data/lib/rubocop/cop/style/non_nil_check.rb +5 -11
  219. data/lib/rubocop/cop/style/not.rb +2 -0
  220. data/lib/rubocop/cop/style/numeric_literals.rb +3 -2
  221. data/lib/rubocop/cop/style/one_class_per_file.rb +115 -0
  222. data/lib/rubocop/cop/style/one_line_conditional.rb +21 -12
  223. data/lib/rubocop/cop/style/operator_method_call.rb +11 -2
  224. data/lib/rubocop/cop/style/parallel_assignment.rb +6 -2
  225. data/lib/rubocop/cop/style/partition_instead_of_double_select.rb +270 -0
  226. data/lib/rubocop/cop/style/percent_literal_delimiters.rb +2 -0
  227. data/lib/rubocop/cop/style/predicate_with_kind.rb +84 -0
  228. data/lib/rubocop/cop/style/preferred_hash_methods.rb +12 -12
  229. data/lib/rubocop/cop/style/proc.rb +3 -2
  230. data/lib/rubocop/cop/style/raise_args.rb +1 -1
  231. data/lib/rubocop/cop/style/reduce_to_hash.rb +184 -0
  232. data/lib/rubocop/cop/style/redundant_argument.rb +2 -0
  233. data/lib/rubocop/cop/style/redundant_begin.rb +3 -3
  234. data/lib/rubocop/cop/style/redundant_condition.rb +5 -2
  235. data/lib/rubocop/cop/style/redundant_each.rb +3 -3
  236. data/lib/rubocop/cop/style/redundant_exception.rb +1 -1
  237. data/lib/rubocop/cop/style/redundant_fetch_block.rb +1 -1
  238. data/lib/rubocop/cop/style/redundant_format.rb +26 -5
  239. data/lib/rubocop/cop/style/redundant_interpolation.rb +11 -2
  240. data/lib/rubocop/cop/style/redundant_interpolation_unfreeze.rb +26 -10
  241. data/lib/rubocop/cop/style/redundant_line_continuation.rb +16 -0
  242. data/lib/rubocop/cop/style/redundant_min_max_by.rb +93 -0
  243. data/lib/rubocop/cop/style/redundant_parentheses.rb +26 -22
  244. data/lib/rubocop/cop/style/redundant_percent_q.rb +5 -3
  245. data/lib/rubocop/cop/style/redundant_regexp_argument.rb +9 -0
  246. data/lib/rubocop/cop/style/redundant_regexp_escape.rb +8 -0
  247. data/lib/rubocop/cop/style/redundant_return.rb +3 -1
  248. data/lib/rubocop/cop/style/redundant_self_assignment_branch.rb +0 -5
  249. data/lib/rubocop/cop/style/redundant_sort.rb +7 -7
  250. data/lib/rubocop/cop/style/redundant_struct_keyword_init.rb +114 -0
  251. data/lib/rubocop/cop/style/reverse_find.rb +51 -0
  252. data/lib/rubocop/cop/style/safe_navigation.rb +7 -7
  253. data/lib/rubocop/cop/style/select_by_kind.rb +158 -0
  254. data/lib/rubocop/cop/style/select_by_range.rb +197 -0
  255. data/lib/rubocop/cop/style/select_by_regexp.rb +51 -21
  256. data/lib/rubocop/cop/style/semicolon.rb +25 -7
  257. data/lib/rubocop/cop/style/single_line_block_params.rb +2 -2
  258. data/lib/rubocop/cop/style/single_line_do_end_block.rb +1 -1
  259. data/lib/rubocop/cop/style/single_line_methods.rb +3 -1
  260. data/lib/rubocop/cop/style/sole_nested_conditional.rb +8 -1
  261. data/lib/rubocop/cop/style/special_global_vars.rb +6 -1
  262. data/lib/rubocop/cop/style/super_arguments.rb +2 -2
  263. data/lib/rubocop/cop/style/symbol_proc.rb +4 -3
  264. data/lib/rubocop/cop/style/tally_method.rb +181 -0
  265. data/lib/rubocop/cop/style/trailing_comma_in_arguments.rb +45 -0
  266. data/lib/rubocop/cop/style/trailing_comma_in_block_args.rb +1 -1
  267. data/lib/rubocop/cop/style/trailing_method_end_statement.rb +1 -0
  268. data/lib/rubocop/cop/style/trailing_underscore_variable.rb +11 -11
  269. data/lib/rubocop/cop/style/unless_else.rb +10 -9
  270. data/lib/rubocop/cop/style/yoda_expression.rb +1 -1
  271. data/lib/rubocop/cop/team.rb +4 -4
  272. data/lib/rubocop/cop/util.rb +2 -3
  273. data/lib/rubocop/cop/utils/format_string.rb +10 -0
  274. data/lib/rubocop/cop/variable_force/branch.rb +30 -6
  275. data/lib/rubocop/cops_documentation_generator.rb +4 -4
  276. data/lib/rubocop/directive_comment.rb +48 -4
  277. data/lib/rubocop/formatter/clang_style_formatter.rb +5 -2
  278. data/lib/rubocop/formatter/disabled_config_formatter.rb +2 -1
  279. data/lib/rubocop/formatter/formatter_set.rb +2 -2
  280. data/lib/rubocop/formatter/junit_formatter.rb +1 -1
  281. data/lib/rubocop/formatter/simple_text_formatter.rb +0 -2
  282. data/lib/rubocop/formatter/tap_formatter.rb +5 -2
  283. data/lib/rubocop/formatter/worst_offenders_formatter.rb +1 -1
  284. data/lib/rubocop/formatter.rb +22 -21
  285. data/lib/rubocop/lsp/diagnostic.rb +18 -33
  286. data/lib/rubocop/lsp/disable_comment_edits.rb +135 -0
  287. data/lib/rubocop/lsp/routes.rb +12 -5
  288. data/lib/rubocop/lsp/runtime.rb +13 -3
  289. data/lib/rubocop/lsp/stdin_runner.rb +8 -17
  290. data/lib/rubocop/magic_comment.rb +20 -0
  291. data/lib/rubocop/mcp/server.rb +200 -0
  292. data/lib/rubocop/options.rb +10 -1
  293. data/lib/rubocop/path_util.rb +14 -2
  294. data/lib/rubocop/plugin/loader.rb +1 -1
  295. data/lib/rubocop/rake_task.rb +1 -1
  296. data/lib/rubocop/remote_config.rb +10 -8
  297. data/lib/rubocop/result_cache.rb +60 -37
  298. data/lib/rubocop/rspec/cop_helper.rb +8 -0
  299. data/lib/rubocop/rspec/shared_contexts.rb +18 -5
  300. data/lib/rubocop/rspec/support.rb +2 -1
  301. data/lib/rubocop/runner.rb +12 -3
  302. data/lib/rubocop/server/cache.rb +6 -29
  303. data/lib/rubocop/server/core.rb +2 -0
  304. data/lib/rubocop/target_finder.rb +1 -1
  305. data/lib/rubocop/target_ruby.rb +31 -14
  306. data/lib/rubocop/version.rb +2 -2
  307. data/lib/rubocop.rb +20 -0
  308. data/lib/ruby_lsp/rubocop/addon.rb +23 -8
  309. data/lib/ruby_lsp/rubocop/runtime_adapter.rb +49 -15
  310. metadata +33 -9
@@ -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
@@ -202,6 +206,7 @@ module RuboCop
202
206
  return false unless node.rescue_type?
203
207
  return false unless (parent = begin_node.parent)
204
208
  return false if parent.if_type? && parent.ternary?
209
+ return false if parent.conditional? && parent.condition == begin_node
205
210
 
206
211
  !parent.type?(:call, :array, :pair)
207
212
  end
@@ -265,6 +270,18 @@ module RuboCop
265
270
  end
266
271
  end
267
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
+
268
285
  def disallowed_one_line_pattern_matching?(begin_node, node)
269
286
  if (parent = begin_node.parent)
270
287
  return false if parent.any_def_type? && parent.endless?
@@ -307,10 +324,10 @@ module RuboCop
307
324
  end
308
325
 
309
326
  def singular_parenthesized_parent?(begin_node)
310
- return true unless begin_node.parent
311
- 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)
312
329
 
313
- parentheses?(begin_node) && begin_node.parent.children.one?
330
+ parent.children.one?
314
331
  end
315
332
 
316
333
  def only_begin_arg?(args)
@@ -318,28 +335,15 @@ module RuboCop
318
335
  end
319
336
 
320
337
  def first_argument?(node)
321
- if first_send_argument?(node) ||
322
- first_super_argument?(node) ||
323
- first_yield_argument?(node)
324
- return true
325
- end
338
+ return true if first_call_argument?(node)
326
339
 
327
340
  node.each_ancestor.any? { |ancestor| first_argument?(ancestor) }
328
341
  end
329
342
 
330
- # @!method first_send_argument?(node)
331
- def_node_matcher :first_send_argument?, <<~PATTERN
332
- ^(send _ _ equal?(%0) ...)
333
- PATTERN
334
-
335
- # @!method first_super_argument?(node)
336
- def_node_matcher :first_super_argument?, <<~PATTERN
337
- ^(super equal?(%0) ...)
338
- PATTERN
339
-
340
- # @!method first_yield_argument?(node)
341
- def_node_matcher :first_yield_argument?, <<~PATTERN
342
- ^(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) ...)}
343
347
  PATTERN
344
348
 
345
349
  def call_chain_starts_with_int?(begin_node, send_node)
@@ -80,8 +80,7 @@ module RuboCop
80
80
  end
81
81
 
82
82
  def string_literal?(node)
83
- node.loc.respond_to?(:begin) && node.loc.respond_to?(:end) &&
84
- node.loc.begin && node.loc.end
83
+ node.loc?(:begin) && node.loc?(:end)
85
84
  end
86
85
 
87
86
  def start_with_percent_q_variant?(string)
@@ -91,7 +90,10 @@ module RuboCop
91
90
  def acceptable_q?(node)
92
91
  src = node.source
93
92
 
94
- 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)
95
97
 
96
98
  src.scan(/\\./).any?(ESCAPED_NON_BACKSLASH)
97
99
  end
@@ -66,6 +66,7 @@ module RuboCop
66
66
  DETERMINISTIC_REGEX.match?(regexp_node.source)
67
67
  end
68
68
 
69
+ # rubocop:disable Metrics/MethodLength
69
70
  def preferred_argument(regexp_node)
70
71
  new_argument = replacement(regexp_node)
71
72
 
@@ -73,6 +74,13 @@ module RuboCop
73
74
  new_argument.gsub!("'", "\\\\'")
74
75
  new_argument.gsub!('\"', '"')
75
76
  quote = "'"
77
+ elsif new_argument.include?("\\'")
78
+ # Add a backslash before single quotes preceded by an even number of backslashes.
79
+ # An even number (including zero) of backslashes before a quote means the quote itself
80
+ # is not escaped.
81
+ # Otherwise an odd number means the quote is already escaped so this doesn't touch it.
82
+ new_argument.gsub!(/(?<!\\)((?:\\\\)*)'/) { "#{::Regexp.last_match(1)}\\'" }
83
+ quote = "'"
76
84
  elsif new_argument.include?('\'')
77
85
  new_argument.gsub!("'", "\\\\'")
78
86
  quote = "'"
@@ -84,6 +92,7 @@ module RuboCop
84
92
 
85
93
  "#{quote}#{new_argument}#{quote}"
86
94
  end
95
+ # rubocop:enable Metrics/MethodLength
87
96
 
88
97
  def replacement(regexp_node)
89
98
  regexp_content = regexp_node.content
@@ -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
@@ -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
@@ -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
@@ -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, '&')
@@ -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