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
@@ -9,9 +9,21 @@ module RuboCop
9
9
  # cop disables on wide ranges of code, that latter contributors to
10
10
  # a file wouldn't be aware of.
11
11
  #
12
- # @example
13
- # # Lint/MissingCopEnableDirective:
14
- # # MaximumRangeSize: .inf
12
+ # You can set `MaximumRangeSize` to define the maximum number of
13
+ # consecutive lines a cop can be disabled for.
14
+ #
15
+ # - `.inf` any size (default)
16
+ # - `0` allows only single-line disables
17
+ # - `1` means the maximum allowed is as follows:
18
+ #
19
+ # [source,ruby]
20
+ # ----
21
+ # # rubocop:disable SomeCop
22
+ # a = 1
23
+ # # rubocop:enable SomeCop
24
+ # ----
25
+ #
26
+ # @example MaximumRangeSize: .inf (default)
15
27
  #
16
28
  # # good
17
29
  # # rubocop:disable Layout/SpaceAroundOperators
@@ -25,9 +37,7 @@ module RuboCop
25
37
  # x= 0
26
38
  # # EOF
27
39
  #
28
- # @example
29
- # # Lint/MissingCopEnableDirective:
30
- # # MaximumRangeSize: 2
40
+ # @example MaximumRangeSize: 2
31
41
  #
32
42
  # # good
33
43
  # # rubocop:disable Layout/SpaceAroundOperators
@@ -31,6 +31,7 @@ module RuboCop
31
31
  end
32
32
  end
33
33
  alias on_numblock on_block
34
+ alias on_itblock on_block
34
35
 
35
36
  private
36
37
 
@@ -39,6 +40,7 @@ module RuboCop
39
40
  {
40
41
  (block (call _recv {:reduce :inject} !sym) _blockargs $(begin ...))
41
42
  (numblock (call _recv {:reduce :inject} !sym) _argscount $(begin ...))
43
+ (itblock (call _recv {:reduce :inject} !sym) _argscount $(begin ...))
42
44
  }
43
45
  PATTERN
44
46
 
@@ -45,6 +45,10 @@ module RuboCop
45
45
  end
46
46
  end
47
47
  end
48
+ alias on_ivasgn on_lvasgn
49
+ alias on_cvasgn on_lvasgn
50
+ alias on_gvasgn on_lvasgn
51
+ alias on_casgn on_lvasgn
48
52
  alias on_or_asgn on_lvasgn
49
53
  alias on_op_asgn on_lvasgn
50
54
  end
@@ -65,7 +65,9 @@ module RuboCop
65
65
 
66
66
  maximum_target_ruby_version 2.7
67
67
 
68
- def on_block(node)
68
+ # NOTE: itblock is not handled because this cop is limited to Ruby <= 2.7
69
+ # via `maximum_target_ruby_version`, so itblock nodes (Ruby 3.4+) are never encountered.
70
+ def on_block(node) # rubocop:disable InternalAffairs/ItblockHandler
69
71
  return unless node.body
70
72
  return unless unsorted_dir_loop?(node.send_node)
71
73
 
@@ -162,7 +162,7 @@ module RuboCop
162
162
  end
163
163
 
164
164
  def allow_receiver?(receiver)
165
- if receiver.numeric_type? || (receiver.send_type? &&
165
+ if receiver.numeric_type? || (receiver.call_type? &&
166
166
  (conversion_method?(receiver.method_name) ||
167
167
  allowed_method_name?(receiver.method_name)))
168
168
  true
@@ -112,22 +112,36 @@ module RuboCop
112
112
 
113
113
  def each_line_range(cop, line_ranges)
114
114
  line_ranges.each_with_index do |line_range, line_range_index|
115
- next if ignore_offense?(line_range)
116
- next if expected_final_disable?(cop, line_range)
115
+ next if should_skip_line_range?(cop, line_range)
117
116
 
118
117
  comment = processed_source.comment_at_line(line_range.begin)
119
- redundant = if all_disabled?(comment)
120
- find_redundant_all(line_range, line_ranges[line_range_index + 1])
121
- elsif department_disabled?(cop, comment)
122
- find_redundant_department(cop, line_range)
123
- else
124
- find_redundant_cop(cop, line_range)
125
- end
118
+ next if skip_directive?(comment)
126
119
 
120
+ next_range = line_ranges[line_range_index + 1]
121
+ redundant = find_redundant_directive(cop, comment, line_range, next_range)
127
122
  yield comment, redundant if redundant
128
123
  end
129
124
  end
130
125
 
126
+ def should_skip_line_range?(cop, line_range)
127
+ ignore_offense?(line_range) || expected_final_disable?(cop, line_range)
128
+ end
129
+
130
+ def skip_directive?(comment)
131
+ directive = DirectiveComment.new(comment)
132
+ directive.push? || directive.pop?
133
+ end
134
+
135
+ def find_redundant_directive(cop, comment, line_range, next_range)
136
+ if all_disabled?(comment)
137
+ find_redundant_all(line_range, next_range)
138
+ elsif department_disabled?(cop, comment)
139
+ find_redundant_department(cop, line_range)
140
+ else
141
+ find_redundant_cop(cop, line_range)
142
+ end
143
+ end
144
+
131
145
  # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
132
146
  def each_already_disabled(cop, line_ranges)
133
147
  line_ranges.each_cons(2) do |previous_range, range|
@@ -1,11 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # The Lint/RedundantCopEnableDirective and Lint/RedundantCopDisableDirective
4
- # cops need to be disabled so as to be able to provide a (bad) example of an
5
- # unneeded enable.
6
-
7
- # rubocop:disable Lint/RedundantCopEnableDirective
8
- # rubocop:disable Lint/RedundantCopDisableDirective
9
3
  module RuboCop
10
4
  module Cop
11
5
  module Lint
@@ -130,6 +124,3 @@ module RuboCop
130
124
  end
131
125
  end
132
126
  end
133
-
134
- # rubocop:enable Lint/RedundantCopDisableDirective
135
- # rubocop:enable Lint/RedundantCopEnableDirective
@@ -19,7 +19,8 @@ module RuboCop
19
19
  # * 2.2+ ... Add `rational` and `complex` above
20
20
  # * 2.7+ ... Add `ruby2_keywords` above
21
21
  # * 3.1+ ... Add `fiber` above
22
- # * 3.2+ ... `set`
22
+ # * 3.2+ ... Add `set` above
23
+ # * 4.0+ ... Add `pathname` above
23
24
  #
24
25
  # This cop target those features.
25
26
  #
@@ -69,7 +70,8 @@ module RuboCop
69
70
  (target_ruby_version >= 2.2 && RUBY_22_LOADED_FEATURES.include?(feature_name)) ||
70
71
  (target_ruby_version >= 2.7 && feature_name == 'ruby2_keywords') ||
71
72
  (target_ruby_version >= 3.1 && feature_name == 'fiber') ||
72
- (target_ruby_version >= 3.2 && feature_name == 'set')
73
+ (target_ruby_version >= 3.2 && feature_name == 'set') ||
74
+ (target_ruby_version >= 4.0 && feature_name == 'pathname')
73
75
  end
74
76
  # rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
75
77
  end
@@ -62,6 +62,22 @@ module RuboCop
62
62
  # do_something if attrs.respond_to?(:[])
63
63
  #
64
64
  # # bad
65
+ # foo&.bar ? foo&.bar.baz : qux
66
+ #
67
+ # # good
68
+ # foo&.bar ? foo.bar.baz : qux
69
+ #
70
+ # # bad
71
+ # if foo&.bar
72
+ # foo&.bar.baz
73
+ # end
74
+ #
75
+ # # good
76
+ # if foo&.bar
77
+ # foo.bar.baz
78
+ # end
79
+ #
80
+ # # bad
65
81
  # while node&.is_a?(BeginNode)
66
82
  # node = node.parent
67
83
  # end
@@ -71,8 +87,9 @@ module RuboCop
71
87
  # node = node.parent
72
88
  # end
73
89
  #
74
- # # good - without `&.` this will always return `true`
90
+ # # good - without `&.` this changes the return value for `nil`
75
91
  # foo&.respond_to?(:to_a)
92
+ # foo&.respond_to?(:class)
76
93
  #
77
94
  # # bad - for `nil`s conversion methods return default values for the type
78
95
  # foo&.to_h || {}
@@ -151,15 +168,15 @@ module RuboCop
151
168
  MSG_NON_NIL = 'Redundant safe navigation on non-nil receiver (detected by analyzing ' \
152
169
  'previous code/method invocations).'
153
170
 
154
- NIL_SPECIFIC_METHODS = (nil.methods - Object.new.methods).to_set.freeze
171
+ NIL_METHODS = nil.methods.to_set.freeze
155
172
 
156
173
  SNAKE_CASE = /\A[[:digit:][:upper:]_]+\z/.freeze
157
174
 
158
175
  GUARANTEED_INSTANCE_METHODS = %i[to_s to_i to_f to_a to_h].freeze
159
176
 
160
- # @!method respond_to_nil_specific_method?(node)
161
- def_node_matcher :respond_to_nil_specific_method?, <<~PATTERN
162
- (csend _ :respond_to? (sym %NIL_SPECIFIC_METHODS))
177
+ # @!method respond_to_nil_method?(node)
178
+ def_node_matcher :respond_to_nil_method?, <<~PATTERN
179
+ (csend _ :respond_to? (sym %NIL_METHODS))
163
180
  PATTERN
164
181
 
165
182
  # @!method conversion_with_default?(node)
@@ -189,7 +206,7 @@ module RuboCop
189
206
 
190
207
  unless assume_receiver_instance_exists?(node.receiver)
191
208
  return if !guaranteed_instance?(node.receiver) && !check?(node)
192
- return if respond_to_nil_specific_method?(node)
209
+ return if respond_to_nil_method?(node)
193
210
  end
194
211
 
195
212
  add_offense(range) { |corrector| corrector.replace(range, '.') }
@@ -77,7 +77,7 @@ module RuboCop
77
77
  PERCENT_CAPITAL_W = '%W'
78
78
  PERCENT_I = '%i'
79
79
  PERCENT_CAPITAL_I = '%I'
80
- ASSIGNMENT_TYPES = %i[lvasgn ivasgn cvasgn gvasgn].freeze
80
+ ASSIGNMENT_TYPES = %i[lvasgn ivasgn cvasgn gvasgn casgn].freeze
81
81
 
82
82
  # @!method array_new?(node)
83
83
  def_node_matcher :array_new?, <<~PATTERN
@@ -144,7 +144,9 @@ module RuboCop
144
144
  expression = node.parent.source_range if node.parent.array_type?
145
145
  [expression, variable.source]
146
146
  elsif !variable.array_type?
147
- [expression, "[#{variable.source}]"]
147
+ replacement = variable.source
148
+ replacement = "[#{replacement}]" if wrap_in_brackets?(node)
149
+ [expression, replacement]
148
150
  elsif redundant_brackets?(node)
149
151
  [expression, remove_brackets(variable)]
150
152
  else
@@ -176,6 +178,10 @@ module RuboCop
176
178
  grandparent&.resbody_type?
177
179
  end
178
180
 
181
+ def wrap_in_brackets?(node)
182
+ node.parent.array_type? && !node.parent.bracketed?
183
+ end
184
+
179
185
  def remove_brackets(array)
180
186
  array_start = array.loc.begin.source
181
187
  elements = *array
@@ -24,10 +24,7 @@ module RuboCop
24
24
  MSG = 'Avoid rescuing the `Exception` class. Perhaps you meant to rescue `StandardError`?'
25
25
 
26
26
  def on_resbody(node)
27
- return unless node.children.first
28
-
29
- rescue_args = node.children.first.children
30
- return unless rescue_args.any? { |a| targets_exception?(a) }
27
+ return unless node.exceptions.any? { |exception| targets_exception?(exception) }
31
28
 
32
29
  add_offense(node)
33
30
  end
@@ -37,20 +37,25 @@ module RuboCop
37
37
  }
38
38
  PATTERN
39
39
 
40
+ # rubocop:disable Metrics/AbcSize
40
41
  def on_send(node)
41
42
  return unless require_safe_navigation?(node)
42
43
 
43
44
  bad_method?(node) do |safe_nav, method|
44
45
  return if nil_methods.include?(method) || PLUS_MINUS_METHODS.include?(node.method_name)
46
+ return if ternary_safe_navigation?(node, safe_nav)
45
47
 
46
48
  begin_range = node.loc.dot || safe_nav.source_range.end
47
49
  location = begin_range.join(node.source_range.end)
48
50
 
49
51
  add_offense(location) do |corrector|
52
+ next if ternary_else_branch?(node, safe_nav)
53
+
50
54
  autocorrect(corrector, offense_range: location, send_node: node)
51
55
  end
52
56
  end
53
57
  end
58
+ # rubocop:enable Metrics/AbcSize
54
59
 
55
60
  private
56
61
 
@@ -61,6 +66,18 @@ module RuboCop
61
66
  parent.rhs != node || parent.lhs.receiver != parent.rhs.receiver
62
67
  end
63
68
 
69
+ def ternary_safe_navigation?(node, safe_nav)
70
+ return false unless (parent = node.parent)
71
+
72
+ parent.if_type? && node.equal?(parent.if_branch) && parent.condition == safe_nav
73
+ end
74
+
75
+ def ternary_else_branch?(node, safe_nav)
76
+ return false unless (parent = node.parent)
77
+
78
+ parent.if_type? && node.equal?(parent.else_branch) && parent.condition == safe_nav
79
+ end
80
+
64
81
  # @param [Parser::Source::Range] offense_range
65
82
  # @param [RuboCop::AST::SendNode] send_node
66
83
  # @return [String]
@@ -3,10 +3,16 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Lint
6
- # Check to make sure that if safe navigation is used in an `&&` or `||` condition,
6
+ # Checks that if safe navigation is used in an `&&` or `||` condition,
7
7
  # consistent and appropriate safe navigation, without excess or deficiency,
8
8
  # is used for all method calls on the same object.
9
9
  #
10
+ # @safety
11
+ # Autocorrection is unsafe because if the receiver is not a local variable
12
+ # but a method call, it may not be idempotent. For example, replacing
13
+ # `foo&.bar` with `foo.bar` could raise `NoMethodError` if `foo` returns
14
+ # `nil` on a subsequent call.
15
+ #
10
16
  # @example
11
17
  # # bad
12
18
  # foo&.bar && foo&.baz
@@ -23,7 +23,15 @@ module RuboCop
23
23
  # # good (method calls possibly can return different results)
24
24
  # hash[foo] = hash[foo]
25
25
  #
26
- # @example AllowRBSInlineAnnotation:true
26
+ # @example AllowRBSInlineAnnotation: false (default)
27
+ # # bad
28
+ # foo = foo #: Integer
29
+ # foo, bar = foo, bar #: Integer
30
+ # Foo = Foo #: Integer
31
+ # hash['foo'] = hash['foo'] #: Integer
32
+ # obj.attr = obj.attr #: Integer
33
+ #
34
+ # @example AllowRBSInlineAnnotation: true
27
35
  # # good
28
36
  # foo = foo #: Integer
29
37
  # foo, bar = foo, bar #: Integer
@@ -108,7 +116,7 @@ module RuboCop
108
116
  value_node = node.last_argument
109
117
  node_arguments = node.arguments[0...-1]
110
118
 
111
- if value_node.send_type? && value_node.method?(:[]) &&
119
+ if value_node.respond_to?(:method?) && value_node.method?(:[]) &&
112
120
  node.receiver == value_node.receiver &&
113
121
  node_arguments.none?(&:call_type?) &&
114
122
  node_arguments == value_node.arguments
@@ -125,13 +125,13 @@ module RuboCop
125
125
  next false if assignment_node.shorthand_asgn?
126
126
  next false unless assignment_node.parent
127
127
 
128
- node_within_block_or_conditional =
129
- node_within_block_or_conditional?(assignment_node.parent, argument.scope.node)
128
+ conditional_assignment =
129
+ conditional_assignment?(assignment_node.parent, argument.scope.node)
130
130
 
131
131
  unless uses_var?(assignment_node, argument.name)
132
132
  # It's impossible to decide whether a branch or block is executed,
133
133
  # so the precise reassignment location is undecidable.
134
- next false if node_within_block_or_conditional
134
+ next false if conditional_assignment
135
135
 
136
136
  yield(assignment.node, location_known)
137
137
  break
@@ -147,13 +147,13 @@ module RuboCop
147
147
  node.source_range.begin_pos
148
148
  end
149
149
 
150
- # Check whether the given node is nested into block or conditional.
150
+ # Check whether the given node is always executed or not
151
151
  #
152
- def node_within_block_or_conditional?(node, stop_search_node)
152
+ def conditional_assignment?(node, stop_search_node)
153
153
  return false if node == stop_search_node
154
154
 
155
- node.conditional? || node.block_type? ||
156
- node_within_block_or_conditional?(node.parent, stop_search_node)
155
+ node.conditional? || node.type?(:block, :rescue) ||
156
+ conditional_assignment?(node.parent, stop_search_node)
157
157
  end
158
158
 
159
159
  # Get argument references without assignments' references
@@ -26,7 +26,23 @@ module RuboCop
26
26
  'and it may be unexpected.'
27
27
  RESTRICT_ON_SEND = %i[new].freeze
28
28
 
29
- STRUCT_METHOD_NAMES = Struct.instance_methods
29
+ # This is based on `Struct.instance_methods.sort` in Ruby 4.0.0.
30
+ STRUCT_METHOD_NAMES = %i[
31
+ ! != !~ <=> == === [] []= __id__ __send__ all? any? chain chunk chunk_while class clone
32
+ collect collect_concat compact count cycle deconstruct deconstruct_keys
33
+ define_singleton_method detect dig display drop drop_while dup each each_cons each_entry
34
+ each_pair each_slice each_with_index each_with_object entries enum_for eql? equal? extend
35
+ filter filter_map find find_all find_index first flat_map freeze frozen? grep grep_v
36
+ group_by hash include? inject inspect instance_eval instance_exec instance_of?
37
+ instance_variable_defined? instance_variable_get instance_variable_set instance_variables
38
+ is_a? itself kind_of? lazy length map max max_by member? members method methods
39
+ min min_by minmax minmax_by nil? none? object_id one? partition private_methods
40
+ protected_methods public_method public_methods public_send reduce reject
41
+ remove_instance_variable respond_to? reverse_each select send singleton_class
42
+ singleton_method singleton_methods size slice_after slice_before slice_when sort sort_by
43
+ sum take take_while tally tap then to_a to_enum to_h to_s to_set uniq values values_at
44
+ yield_self zip
45
+ ].freeze
30
46
  STRUCT_MEMBER_NAME_TYPES = %i[sym str].freeze
31
47
 
32
48
  # @!method struct_new(node)
@@ -26,7 +26,31 @@ module RuboCop
26
26
  "#{diagnostic.message}\n(Using Ruby #{ruby_version} parser; " \
27
27
  'configure using `TargetRubyVersion` parameter, under `AllCops`)'
28
28
  end
29
- add_offense(diagnostic.location, message: message, severity: diagnostic.level)
29
+ location = diagnostic_location(diagnostic.location)
30
+ add_offense(location, message: message, severity: diagnostic.level)
31
+ end
32
+
33
+ # Expand zero-length diagnostic ranges so that editors and formatters
34
+ # can display them. This typically occurs when the parser reports
35
+ # `unexpected token $end` at EOF.
36
+ def diagnostic_location(location)
37
+ return location if location.size.positive?
38
+
39
+ source_buffer = location.source_buffer
40
+ if location.end_pos < source_buffer.source.size
41
+ location.resize(1)
42
+ elsif location.begin_pos.positive?
43
+ location.adjust(begin_pos: -1)
44
+ else
45
+ location
46
+ end
47
+ end
48
+
49
+ # Override to skip multiline_ranges check which requires AST.
50
+ # Syntax errors mean the AST is nil, so we go directly to
51
+ # the EOL comment insertion path.
52
+ def disable_offense(offense_range)
53
+ disable_offense_with_eol_or_surround_comment(offense_range)
30
54
  end
31
55
 
32
56
  def add_offense_from_error(error)
@@ -5,27 +5,23 @@ module RuboCop
5
5
  module Lint
6
6
  # Checks to make sure `#to_json` includes an optional argument.
7
7
  # When overriding `#to_json`, callers may invoke JSON
8
- # generation via `JSON.generate(your_obj)`. Since `JSON#generate` allows
8
+ # generation via `JSON.generate(your_obj)`. Since `JSON#generate` allows
9
9
  # for an optional argument, your method should too.
10
10
  #
11
11
  # @example
12
- # class Point
13
- # attr_reader :x, :y
14
- #
15
- # # bad, incorrect arity
16
- # def to_json
17
- # JSON.generate([x, y])
18
- # end
12
+ # # bad - incorrect arity
13
+ # def to_json
14
+ # JSON.generate([x, y])
15
+ # end
19
16
  #
20
- # # good, preserving args
21
- # def to_json(*args)
22
- # JSON.generate([x, y], *args)
23
- # end
17
+ # # good - preserving args
18
+ # def to_json(*args)
19
+ # JSON.generate([x, y], *args)
20
+ # end
24
21
  #
25
- # # good, discarding args
26
- # def to_json(*_args)
27
- # JSON.generate([x, y])
28
- # end
22
+ # # good - discarding args
23
+ # def to_json(*_args)
24
+ # JSON.generate([x, y])
29
25
  # end
30
26
  #
31
27
  class ToJSON < Base
@@ -32,6 +32,7 @@ module RuboCop
32
32
  include RangeHelp
33
33
 
34
34
  MSG = 'Avoid leaving a trailing comma in attribute declarations.'
35
+ RESTRICT_ON_SEND = %i[attr_reader attr_writer attr_accessor attr].freeze
35
36
 
36
37
  def on_send(node)
37
38
  return unless node.attribute_accessor? && node.last_argument.def_type?
@@ -120,6 +120,7 @@ module RuboCop
120
120
  check_return_values(node)
121
121
  end
122
122
  alias on_numblock on_block
123
+ alias on_itblock on_block
123
124
 
124
125
  private
125
126
 
@@ -78,6 +78,7 @@ module RuboCop
78
78
  }
79
79
  PATTERN
80
80
 
81
+ # rubocop:disable Metrics/MethodLength
81
82
  def flow_expression?(node)
82
83
  return report_on_flow_command?(node) if flow_command?(node)
83
84
 
@@ -89,12 +90,14 @@ module RuboCop
89
90
  check_if(node)
90
91
  when :case, :case_match
91
92
  check_case(node)
92
- when :def
93
+ when :def, :defs
93
94
  register_redefinition(node)
95
+ false
94
96
  else
95
97
  false
96
98
  end
97
99
  end
100
+ # rubocop:enable Metrics/MethodLength
98
101
 
99
102
  def check_if(node)
100
103
  if_branch = node.if_branch
@@ -113,8 +116,7 @@ module RuboCop
113
116
  end
114
117
 
115
118
  def register_redefinition(node)
116
- @redefined << node.method_name if redefinable_flow_method? node.method_name
117
- false
119
+ @redefined << node.method_name if redefinable_flow_method?(node.method_name)
118
120
  end
119
121
 
120
122
  def instance_eval_block?(node)
@@ -0,0 +1,113 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Lint
6
+ # Checks for unreachable `in` pattern branches in `case...in` statements.
7
+ #
8
+ # An `in` branch is unreachable when a previous branch uses an unguarded
9
+ # catch-all pattern that matches any value unconditionally. Any `in` branches
10
+ # (and `else`) that follow such a catch-all are dead code.
11
+ #
12
+ # A catch-all pattern is one of:
13
+ #
14
+ # * A bare variable capture (`in x`)
15
+ # * An underscore (`in _`)
16
+ # * A pattern alias where the left side is a catch-all (`in _ => y`)
17
+ # * An alternation pattern where at least one alternative is a catch-all
18
+ # (`in _ | Integer`)
19
+ #
20
+ # NOTE: A catch-all pattern with a guard clause (e.g., `in _ if condition`)
21
+ # does NOT make subsequent branches unreachable because the guard might
22
+ # not be satisfied.
23
+ #
24
+ # @example
25
+ #
26
+ # # bad
27
+ # case value
28
+ # in Integer
29
+ # handle_integer
30
+ # in x
31
+ # handle_other
32
+ # in String
33
+ # handle_string
34
+ # else
35
+ # handle_else
36
+ # end
37
+ #
38
+ # # good
39
+ # case value
40
+ # in Integer
41
+ # handle_integer
42
+ # in String
43
+ # handle_string
44
+ # in x
45
+ # handle_other
46
+ # end
47
+ #
48
+ # # bad - else is unreachable after catch-all
49
+ # case value
50
+ # in Integer
51
+ # handle_integer
52
+ # in _
53
+ # handle_other
54
+ # else
55
+ # handle_else
56
+ # end
57
+ #
58
+ # # good - guard clause means catch-all might not match
59
+ # case value
60
+ # in x if x.positive?
61
+ # handle_positive
62
+ # in Integer
63
+ # handle_integer
64
+ # else
65
+ # handle_other
66
+ # end
67
+ #
68
+ class UnreachablePatternBranch < Base
69
+ extend TargetRubyVersion
70
+
71
+ MSG = 'Unreachable `in` pattern branch detected.'
72
+ MSG_ELSE = 'Unreachable `else` branch detected.'
73
+
74
+ minimum_target_ruby_version 2.7
75
+
76
+ def on_case_match(case_node)
77
+ catch_all_found = false
78
+
79
+ case_node.in_pattern_branches.each do |in_pattern_node|
80
+ if catch_all_found
81
+ add_offense(in_pattern_node)
82
+ next
83
+ end
84
+
85
+ pattern = in_pattern_node.pattern
86
+ guard = in_pattern_node.children[1]
87
+
88
+ catch_all_found = true if catch_all_pattern?(pattern) && guard.nil?
89
+ end
90
+
91
+ return unless catch_all_found && case_node.else?
92
+
93
+ add_offense(case_node.loc.else, message: MSG_ELSE)
94
+ end
95
+
96
+ private
97
+
98
+ def catch_all_pattern?(pattern)
99
+ case pattern.type
100
+ when :match_var
101
+ true
102
+ when :match_as, :begin
103
+ catch_all_pattern?(pattern.children[0])
104
+ when :match_alt
105
+ pattern.children.any? { |child| catch_all_pattern?(child) }
106
+ else
107
+ false
108
+ end
109
+ end
110
+ end
111
+ end
112
+ end
113
+ end