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
@@ -6,18 +6,18 @@ module RuboCop
6
6
  # Checks for use of the lambda.(args) syntax.
7
7
  #
8
8
  # @example EnforcedStyle: call (default)
9
- # # bad
10
- # lambda.(x, y)
9
+ # # bad
10
+ # lambda.(x, y)
11
11
  #
12
- # # good
13
- # lambda.call(x, y)
12
+ # # good
13
+ # lambda.call(x, y)
14
14
  #
15
15
  # @example EnforcedStyle: braces
16
- # # bad
17
- # lambda.call(x, y)
16
+ # # bad
17
+ # lambda.call(x, y)
18
18
  #
19
- # # good
20
- # lambda.(x, y)
19
+ # # good
20
+ # lambda.(x, y)
21
21
  class LambdaCall < Base
22
22
  include ConfigurableEnforcedStyle
23
23
  extend AutoCorrector
@@ -176,7 +176,7 @@ module RuboCop
176
176
  if first_non_comment_token
177
177
  0...first_non_comment_token.line
178
178
  else
179
- (0..)
179
+ 0..
180
180
  end
181
181
  end
182
182
 
@@ -299,7 +299,7 @@ module RuboCop
299
299
  end
300
300
 
301
301
  def supported_capitalizations
302
- cop_config['SupportedCapitalizations'].map(&:to_sym)
302
+ @supported_capitalizations ||= cop_config['SupportedCapitalizations'].map(&:to_sym).freeze
303
303
  end
304
304
  end
305
305
  end
@@ -0,0 +1,123 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Style
6
+ # Checks for `map { |x| x.to_s }.join` and similar calls where the
7
+ # `map` is redundant because `Array#join` implicitly calls `#to_s` on
8
+ # each element.
9
+ #
10
+ # @safety
11
+ # This cop is unsafe because it cannot guarantee that the receiver
12
+ # is an `Array` by static analysis. If the receiver does not have
13
+ # an `Array#join`-compatible implementation (i.e. one that calls
14
+ # `#to_s` on elements), the correction may change behavior.
15
+ #
16
+ # @example
17
+ # # bad
18
+ # array.map(&:to_s).join(', ')
19
+ #
20
+ # # bad
21
+ # array.map { |x| x.to_s }.join(', ')
22
+ #
23
+ # # bad
24
+ # array.collect(&:to_s).join
25
+ #
26
+ # # good
27
+ # array.join(', ')
28
+ #
29
+ # # good
30
+ # array.join
31
+ #
32
+ class MapJoin < Base
33
+ extend AutoCorrector
34
+ include RangeHelp
35
+
36
+ MSG = 'Remove redundant `%<method>s(&:to_s)` before `join`.'
37
+ RESTRICT_ON_SEND = %i[join].freeze
38
+
39
+ # map(&:to_s).join(...)
40
+ # @!method map_to_s_join?(node)
41
+ def_node_matcher :map_to_s_join?, <<~PATTERN
42
+ (call
43
+ $(call _ ${:map :collect} (block_pass (sym :to_s)))
44
+ :join ...)
45
+ PATTERN
46
+
47
+ # map { |x| x.to_s }.join(...)
48
+ # @!method map_to_s_block_join?(node)
49
+ def_node_matcher :map_to_s_block_join?, <<~PATTERN
50
+ (call
51
+ $(block
52
+ (call _ ${:map :collect})
53
+ (args (arg _x))
54
+ (send (lvar _x) :to_s))
55
+ :join ...)
56
+ PATTERN
57
+
58
+ # map { _1.to_s }.join(...)
59
+ # @!method map_to_s_numblock_join?(node)
60
+ def_node_matcher :map_to_s_numblock_join?, <<~PATTERN
61
+ (call
62
+ $(numblock
63
+ (call _ ${:map :collect})
64
+ 1
65
+ (send (lvar :_1) :to_s))
66
+ :join ...)
67
+ PATTERN
68
+
69
+ # map { it.to_s }.join(...)
70
+ # @!method map_to_s_itblock_join?(node)
71
+ def_node_matcher :map_to_s_itblock_join?, <<~PATTERN
72
+ (call
73
+ $(itblock
74
+ (call _ ${:map :collect})
75
+ :it
76
+ (send (lvar :it) :to_s))
77
+ :join ...)
78
+ PATTERN
79
+
80
+ def on_send(node)
81
+ map_to_s_join?(node) { |m, n| register_offense(node, m, n) } ||
82
+ map_to_s_block_join?(node) { |m, n| register_offense(node, m, n) } ||
83
+ map_to_s_numblock_join?(node) { |m, n| register_offense(node, m, n) } ||
84
+ map_to_s_itblock_join?(node) { |m, n| register_offense(node, m, n) }
85
+ end
86
+ alias on_csend on_send
87
+
88
+ private
89
+
90
+ def register_offense(join_node, map_node, method_name)
91
+ map_send = map_node.any_block_type? ? map_node.send_node : map_node
92
+ message = format(MSG, method: method_name)
93
+
94
+ add_offense(map_send.loc.selector, message: message) do |corrector|
95
+ remove_map_call(corrector, join_node, map_node, map_send)
96
+ end
97
+ end
98
+
99
+ def remove_map_call(corrector, join_node, map_node, map_send)
100
+ receiver = map_send.receiver
101
+ if receiver
102
+ corrector.replace(removal_range(receiver, map_node, map_send), '')
103
+ else
104
+ corrector.replace(no_receiver_range(map_node, join_node), '')
105
+ end
106
+ end
107
+
108
+ def removal_range(receiver, map_node, map_send)
109
+ start_pos = if receiver.last_line < map_send.loc.dot.line
110
+ receiver.source_range.end_pos
111
+ else
112
+ map_send.loc.dot.begin_pos
113
+ end
114
+ range_between(start_pos, map_node.source_range.end_pos)
115
+ end
116
+
117
+ def no_receiver_range(map_node, join_node)
118
+ range_between(map_node.source_range.begin_pos, join_node.loc.dot.end_pos)
119
+ end
120
+ end
121
+ end
122
+ end
123
+ end
@@ -33,13 +33,26 @@ module RuboCop
33
33
  end
34
34
 
35
35
  def included_macros_list
36
- cop_config.fetch('IncludedMacros', []).map(&:to_sym)
36
+ @included_macros_list ||= cop_config.fetch('IncludedMacros', []).map(&:to_sym).freeze
37
+ end
38
+
39
+ def included_macro_patterns
40
+ @included_macro_patterns ||=
41
+ cop_config.fetch('IncludedMacroPatterns', [])
42
+ .map { |pattern| Regexp.new(pattern) }.freeze
43
+ end
44
+
45
+ def matches_included_macro_pattern?(method_name)
46
+ included_macro_patterns.any? do |pattern|
47
+ pattern.match?(method_name.to_s)
48
+ end
37
49
  end
38
50
 
39
51
  def ignored_macro?(node)
40
52
  cop_config['IgnoreMacros'] &&
41
53
  node.macro? &&
42
- !included_macros_list.include?(node.method_name)
54
+ !included_macros_list.include?(node.method_name) &&
55
+ !matches_included_macro_pattern?(node.method_name)
43
56
  end
44
57
  end
45
58
  end
@@ -9,17 +9,20 @@ module RuboCop
9
9
  # In the default style (require_parentheses), macro methods are allowed.
10
10
  # Additional methods can be added to the `AllowedMethods` or
11
11
  # `AllowedPatterns` list. These options are valid only in the default
12
- # style. Macros can be included by either setting `IgnoreMacros` to false
13
- # or adding specific macros to the `IncludedMacros` list.
12
+ # style. Macros can be included by either setting `IgnoreMacros` to false,
13
+ # adding specific macros to the `IncludedMacros` list, or using
14
+ # `IncludedMacroPatterns` for pattern-based matching.
14
15
  #
15
16
  # Precedence of options is as follows:
16
17
  #
17
18
  # 1. `AllowedMethods`
18
19
  # 2. `AllowedPatterns`
19
20
  # 3. `IncludedMacros`
21
+ # 4. `IncludedMacroPatterns`
20
22
  #
21
- # If a method is listed in both `IncludedMacros` and `AllowedMethods`,
22
- # then the latter takes precedence (that is, the method is allowed).
23
+ # If a method is listed in both `IncludedMacros`/`IncludedMacroPatterns`
24
+ # and `AllowedMethods`, then the latter takes precedence (that is, the
25
+ # method is allowed).
23
26
  #
24
27
  # In the alternative style (omit_parentheses), there are three additional
25
28
  # options.
@@ -148,6 +151,16 @@ module RuboCop
148
151
  # # still enforces parentheses on other methods
149
152
  # array.delete(e)
150
153
  #
154
+ # @example IncludedMacroPatterns: ["^assert", "^refute"]
155
+ #
156
+ # # bad
157
+ # assert_equal 'test', x
158
+ # refute_nil value
159
+ #
160
+ # # good
161
+ # assert_equal('test', x)
162
+ # refute_nil(value)
163
+ #
151
164
  # @example AllowParenthesesInMultilineCall: false (default)
152
165
  #
153
166
  # # bad
@@ -103,8 +103,6 @@ module RuboCop
103
103
  MSG_MISSING = 'Use def with parentheses when there are parameters.'
104
104
 
105
105
  def on_def(node)
106
- return if forced_parentheses?(node)
107
-
108
106
  args = node.arguments
109
107
 
110
108
  if require_parentheses?(args)
@@ -113,10 +111,10 @@ module RuboCop
113
111
  else
114
112
  correct_style_detected
115
113
  end
114
+ elsif forced_parentheses?(node)
115
+ correct_style_detected
116
116
  elsif parentheses?(args)
117
117
  unwanted_parentheses(args)
118
- else
119
- correct_style_detected
120
118
  end
121
119
  end
122
120
  alias on_defs on_def
@@ -0,0 +1,107 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Style
6
+ # Checks for usage of `Module` methods returning arrays that can be replaced
7
+ # with equivalent predicates.
8
+ #
9
+ # Calling a method returning an array then checking if an element is inside
10
+ # it is much slower than using an equivalent predicate method. For example,
11
+ # `instance_methods.include?` will return an array of all public and protected
12
+ # instance methods in the module, then check if a given method is inside that
13
+ # array, while `method_defined?` will do direct method lookup, which is much
14
+ # faster and consumes less memory.
15
+ #
16
+ # @example
17
+ # # bad
18
+ # Array.instance_methods.include?(:size)
19
+ # Array.instance_methods.member?(:size)
20
+ # Array.instance_methods(true).include?(:size)
21
+ #
22
+ # Array.instance_methods(false).include?(:find)
23
+ #
24
+ # # good
25
+ # Array.method_defined?(:size)
26
+ #
27
+ # Array.method_defined?(:find, false)
28
+ #
29
+ # # bad
30
+ # Array.class_variables.include?(:foo)
31
+ # Array.constants.include?(:foo)
32
+ # Array.private_instance_methods.include?(:foo)
33
+ # Array.protected_instance_methods.include?(:foo)
34
+ # Array.public_instance_methods.include?(:foo)
35
+ #
36
+ # # good
37
+ # Array.class_variable_defined?(:foo)
38
+ # Array.const_defined?(:foo)
39
+ # Array.private_method_defined?(:foo)
40
+ # Array.protected_method_defined?(:foo)
41
+ # Array.public_method_defined?(:foo)
42
+ #
43
+ class ModuleMemberExistenceCheck < Base
44
+ extend AutoCorrector
45
+
46
+ MSG = 'Use `%<replacement>s` instead.'
47
+
48
+ # @!method module_member_inclusion?(node)
49
+ def_node_matcher :module_member_inclusion?, <<~PATTERN
50
+ (call
51
+ {(call _ %METHODS_WITH_INHERIT_PARAM _?) (call _ %METHODS_WITHOUT_INHERIT_PARAM)}
52
+ {:include? :member?}
53
+ _)
54
+ PATTERN
55
+
56
+ METHOD_REPLACEMENTS = {
57
+ class_variables: :class_variable_defined?,
58
+ constants: :const_defined?,
59
+ instance_methods: :method_defined?,
60
+ private_instance_methods: :private_method_defined?,
61
+ protected_instance_methods: :protected_method_defined?,
62
+ public_instance_methods: :public_method_defined?
63
+ }.freeze
64
+
65
+ METHODS_WITHOUT_INHERIT_PARAM = Set[:class_variables].freeze
66
+ METHODS_WITH_INHERIT_PARAM =
67
+ (METHOD_REPLACEMENTS.keys.to_set - METHODS_WITHOUT_INHERIT_PARAM).freeze
68
+
69
+ RESTRICT_ON_SEND = METHOD_REPLACEMENTS.keys.freeze
70
+
71
+ def on_send(node)
72
+ return unless (parent = node.parent)
73
+ return unless module_member_inclusion?(parent)
74
+ return unless simple_method_argument?(node) && simple_method_argument?(parent)
75
+
76
+ offense_range = node.location.selector.join(parent.source_range.end)
77
+ replacement = replacement_for(node, parent)
78
+
79
+ add_offense(offense_range, message: format(MSG, replacement: replacement)) do |corrector|
80
+ corrector.replace(offense_range, replacement)
81
+ end
82
+ end
83
+ alias on_csend on_send
84
+
85
+ private
86
+
87
+ def replacement_for(node, parent)
88
+ replacement_method = METHOD_REPLACEMENTS.fetch(node.method_name)
89
+ without_inherit_param = METHODS_WITHOUT_INHERIT_PARAM.include?(node.method_name)
90
+
91
+ if without_inherit_param || node.first_argument.nil? || node.first_argument.true_type?
92
+ "#{replacement_method}(#{parent.first_argument.source})"
93
+ else
94
+ "#{replacement_method}(#{parent.first_argument.source}, #{node.first_argument.source})"
95
+ end
96
+ end
97
+
98
+ def simple_method_argument?(node)
99
+ return false if node.splat_argument? || node.block_argument?
100
+ return false if node.first_argument&.hash_type?
101
+
102
+ true
103
+ end
104
+ end
105
+ end
106
+ end
107
+ end
@@ -3,7 +3,9 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Style
6
- # Checks for uses of the `then` keyword in multi-line if statements.
6
+ # Checks for uses of the `then` keyword in multi-line `if` statements.
7
+ # In multi-line `if` statements, `then` is redundant because the newline
8
+ # already separates the condition from the body.
7
9
  #
8
10
  # @example
9
11
  # # bad
@@ -21,8 +23,6 @@ module RuboCop
21
23
  include RangeHelp
22
24
  extend AutoCorrector
23
25
 
24
- NON_MODIFIER_THEN = /then\s*(#.*)?$/.freeze
25
-
26
26
  MSG = 'Do not use `then` for multi-line `%<keyword>s`.'
27
27
 
28
28
  def on_normal_if_unless(node)
@@ -36,7 +36,7 @@ module RuboCop
36
36
  private
37
37
 
38
38
  def non_modifier_then?(node)
39
- NON_MODIFIER_THEN.match?(node.loc.begin&.source_line)
39
+ node.then? && node.loc.begin.line != node.if_branch&.loc&.line
40
40
  end
41
41
  end
42
42
  end
@@ -75,6 +75,8 @@ module RuboCop
75
75
  end
76
76
 
77
77
  def correction_exceeds_max_line_length?(node)
78
+ return false unless max_line_length
79
+
78
80
  indentation_width(node) + definition_width(node) > max_line_length
79
81
  end
80
82
 
@@ -85,10 +87,6 @@ module RuboCop
85
87
  def definition_width(node)
86
88
  node.source_range.begin.join(node.arguments.source_range.end).length
87
89
  end
88
-
89
- def max_line_length
90
- config.for_cop('Layout/LineLength')['Max'] || 120
91
- end
92
90
  end
93
91
  end
94
92
  end
@@ -125,7 +125,7 @@ module RuboCop
125
125
  MSG = 'Freeze mutable objects assigned to constants.'
126
126
 
127
127
  def on_casgn(node)
128
- if node.expression.nil? # This is only the case for `CONST += ...` or similarg66
128
+ if node.expression.nil? # This is only the case for `CONST += ...` or similar
129
129
  parent = node.parent
130
130
  return unless parent.or_asgn_type? # We only care about `CONST ||= ...`
131
131
 
@@ -0,0 +1,220 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Style
6
+ # Identifies usages of `arr[arr.length - n]`, `arr[arr.size - n]`, or
7
+ # `arr[arr.count - n]` and suggests to change them to use `arr[-n]` instead.
8
+ # Also handles range patterns like `arr[0..(arr.length - n)]`.
9
+ #
10
+ # The cop recognizes preserving methods (`sort`, `reverse`, `shuffle`, `rotate`)
11
+ # and their combinations, allowing safe replacement when the receiver matches.
12
+ # It works with variables, instance variables, class variables, and constants.
13
+ #
14
+ # @example
15
+ # # bad
16
+ # arr[arr.count - 2]
17
+ # arr[0..(arr.length - 2)]
18
+ # arr[0...(arr.length - 4)]
19
+ # arr.sort[arr.reverse.length - 2]
20
+ # arr.sort.reverse[arr.sort.size - 2]
21
+ #
22
+ # # good
23
+ # arr[-2]
24
+ # arr[0..-2]
25
+ # arr[0...-4]
26
+ # arr.sort[-2]
27
+ # arr.sort.reverse[-2]
28
+ #
29
+ class NegativeArrayIndex < Base
30
+ extend AutoCorrector
31
+ include RangeHelp
32
+
33
+ MSG = 'Use `%<receiver>s[-%<index>s]` instead of `%<current>s`.'
34
+ MSG_RANGE = 'Use `%<receiver>s[%<start>s%<range_op>s-%<index>s]` instead of `%<current>s`.'
35
+ RESTRICT_ON_SEND = %i[[]].freeze
36
+
37
+ LENGTH_METHODS = %i[length size count].freeze
38
+
39
+ PRESERVING_METHODS = %i[sort reverse shuffle rotate].freeze
40
+
41
+ # @!method length_subtraction?(node)
42
+ def_node_matcher :length_subtraction?, <<~PATTERN
43
+ (send
44
+ (send $_ {:length :size :count}) :-
45
+ (int $_))
46
+ PATTERN
47
+
48
+ def on_send(node)
49
+ return if node.arguments.empty?
50
+
51
+ index_arg = node.first_argument
52
+ range_node = extract_range_from_begin(index_arg)
53
+ if range_with_length_subtraction?(range_node, node.receiver)
54
+ receiver = node.receiver.source
55
+ return handle_range_pattern(receiver, range_node, index_arg)
56
+ end
57
+
58
+ handle_simple_index_pattern(node, index_arg)
59
+ end
60
+
61
+ alias on_csend on_send
62
+
63
+ private
64
+
65
+ def handle_simple_index_pattern(node, index_arg)
66
+ length_receiver, negative_index = length_subtraction?(index_arg)
67
+
68
+ return unless negative_index&.positive?
69
+ return unless receivers_match?(length_receiver, node.receiver)
70
+
71
+ add_offense_for_subtraction(node, index_arg, negative_index)
72
+ end
73
+
74
+ def extract_range_from_begin(node)
75
+ node.begin_type? ? node.children.first : node
76
+ end
77
+
78
+ def extract_inner_end(node)
79
+ node.children.size == 1 ? node.children.first : node
80
+ end
81
+
82
+ def add_offense_for_subtraction(node, index_arg, negative_index)
83
+ receiver = node.receiver.source
84
+ offense_range = index_arg.source_range
85
+ current = "#{receiver}[#{index_arg.source}]"
86
+
87
+ message = format(MSG, receiver: receiver, index: negative_index, current: current)
88
+
89
+ add_offense(offense_range, message: message) do |corrector|
90
+ corrector.replace(offense_range, "-#{negative_index}")
91
+ end
92
+ end
93
+
94
+ def range_with_length_subtraction?(range_node, array_receiver)
95
+ return false unless range_node.range_type?
96
+
97
+ range_end = range_node.end
98
+ range_start = range_node.begin
99
+ return false unless range_end && range_start
100
+
101
+ return false unless preserving_method?(range_start)
102
+
103
+ inner_end = extract_inner_end(range_end)
104
+ length_receiver, negative_index = length_subtraction?(inner_end)
105
+
106
+ return false unless negative_index&.positive?
107
+
108
+ receivers_match_strict?(length_receiver, array_receiver)
109
+ end
110
+
111
+ def handle_range_pattern(receiver, range_node, index_arg)
112
+ range_end = range_node.end
113
+ inner_end = extract_inner_end(range_end)
114
+ _length_receiver, negative_index = length_subtraction?(inner_end)
115
+
116
+ message, replacement = build_range_offense_data(
117
+ receiver, range_node, range_end, inner_end, negative_index, index_arg
118
+ )
119
+
120
+ add_offense(range_end, message: message) do |corrector|
121
+ corrector.replace(index_arg, replacement)
122
+ end
123
+ end
124
+
125
+ # rubocop:disable Metrics/ParameterLists
126
+ def build_range_offense_data(receiver, range_node, range_end, inner_end, negative_index,
127
+ index_arg)
128
+ range_op = range_node.erange_type? ? '...' : '..'
129
+ range_start = range_node.begin.source
130
+
131
+ range_without_parens =
132
+ build_range_without_parens(range_start, range_op, range_end, inner_end)
133
+ current_source = build_current_source(receiver, range_without_parens, index_arg)
134
+ start, index = format_range_message_parts(range_start, negative_index, index_arg)
135
+
136
+ message = build_message_for_range(receiver, start, range_op, index, current_source)
137
+ replacement = build_replacement_string(range_start, range_op, negative_index, index_arg)
138
+
139
+ [message, replacement]
140
+ end
141
+ # rubocop:enable Metrics/ParameterLists
142
+
143
+ def format_range_message_parts(range_start, negative_index, index_arg)
144
+ has_parentheses = index_arg.begin_type?
145
+ start = has_parentheses ? "(#{range_start}" : range_start
146
+ index = has_parentheses ? "#{negative_index})" : negative_index
147
+
148
+ [start, index]
149
+ end
150
+
151
+ def build_message_for_range(receiver, start, range_op, index, current)
152
+ format(
153
+ MSG_RANGE,
154
+ receiver: receiver, start: start, range_op: range_op, index: index, current: current
155
+ )
156
+ end
157
+
158
+ def build_replacement_string(range_start, range_op, negative_index, index_arg)
159
+ has_parentheses = index_arg.begin_type?
160
+
161
+ if has_parentheses
162
+ "(#{range_start}#{range_op}-#{negative_index})"
163
+ else
164
+ "#{range_start}#{range_op}-#{negative_index}"
165
+ end
166
+ end
167
+
168
+ def build_current_source(receiver, range_without_parens, index_arg)
169
+ has_parentheses = index_arg.begin_type?
170
+
171
+ if has_parentheses
172
+ "#{receiver}[(#{range_without_parens})]"
173
+ else
174
+ "#{receiver}[#{range_without_parens}]"
175
+ end
176
+ end
177
+
178
+ def build_range_without_parens(range_start, range_op, range_end, inner_end)
179
+ end_expression = range_end.begin_type? ? range_end.source : inner_end.source
180
+
181
+ "#{range_start}#{range_op}#{end_expression}"
182
+ end
183
+
184
+ def receivers_match?(length_receiver, array_receiver)
185
+ return array_receiver.self_type? unless length_receiver
186
+
187
+ unless preserving_method?(array_receiver) && preserving_method?(length_receiver)
188
+ return false
189
+ end
190
+ return true if length_receiver.source == array_receiver.source
191
+
192
+ !extract_base_receiver(array_receiver).nil?
193
+ end
194
+
195
+ def receivers_match_strict?(length_receiver, array_receiver)
196
+ preserving_method?(array_receiver) &&
197
+ length_receiver.source == array_receiver.source
198
+ end
199
+
200
+ def extract_base_receiver(node)
201
+ receiver = node.receiver
202
+
203
+ return nil unless receiver
204
+ return receiver unless receiver.receiver
205
+
206
+ extract_base_receiver(receiver)
207
+ end
208
+
209
+ def preserving_method?(node)
210
+ return true if node.receiver.nil?
211
+
212
+ method_name = node.method_name
213
+ return false unless PRESERVING_METHODS.include?(method_name)
214
+
215
+ preserving_method?(node.receiver)
216
+ end
217
+ end
218
+ end
219
+ end
220
+ end