rubocop 1.79.2 → 1.87.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (376) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE.txt +1 -1
  3. data/README.md +2 -2
  4. data/config/default.yml +185 -20
  5. data/config/obsoletion.yml +9 -0
  6. data/exe/rubocop +1 -8
  7. data/lib/rubocop/cache_config.rb +29 -0
  8. data/lib/rubocop/cli/command/auto_generate_config.rb +30 -4
  9. data/lib/rubocop/cli/command/list_enabled_cops_for.rb +40 -0
  10. data/lib/rubocop/cli/command/lsp.rb +1 -1
  11. data/lib/rubocop/cli/command/mcp.rb +19 -0
  12. data/lib/rubocop/cli/command/show_cops.rb +2 -2
  13. data/lib/rubocop/cli/command/show_docs_url.rb +4 -8
  14. data/lib/rubocop/cli/command/suggest_extensions.rb +1 -1
  15. data/lib/rubocop/cli.rb +35 -9
  16. data/lib/rubocop/comment_config.rb +59 -17
  17. data/lib/rubocop/config.rb +14 -10
  18. data/lib/rubocop/config_finder.rb +1 -1
  19. data/lib/rubocop/config_loader.rb +37 -23
  20. data/lib/rubocop/config_loader_resolver.rb +20 -10
  21. data/lib/rubocop/config_obsoletion/extracted_cop.rb +4 -2
  22. data/lib/rubocop/config_store.rb +7 -2
  23. data/lib/rubocop/config_validator.rb +1 -1
  24. data/lib/rubocop/cop/autocorrect_logic.rb +10 -5
  25. data/lib/rubocop/cop/base.rb +8 -2
  26. data/lib/rubocop/cop/bundler/gem_version.rb +28 -28
  27. data/lib/rubocop/cop/bundler/ordered_gems.rb +1 -2
  28. data/lib/rubocop/cop/correctors/alignment_corrector.rb +26 -7
  29. data/lib/rubocop/cop/correctors/condition_corrector.rb +1 -1
  30. data/lib/rubocop/cop/correctors/for_to_each_corrector.rb +7 -2
  31. data/lib/rubocop/cop/correctors/multiline_literal_brace_corrector.rb +1 -5
  32. data/lib/rubocop/cop/correctors/parentheses_corrector.rb +33 -2
  33. data/lib/rubocop/cop/correctors/percent_literal_corrector.rb +2 -2
  34. data/lib/rubocop/cop/correctors.rb +28 -0
  35. data/lib/rubocop/cop/documentation.rb +2 -3
  36. data/lib/rubocop/cop/exclude_limit.rb +31 -5
  37. data/lib/rubocop/cop/gemspec/duplicated_assignment.rb +2 -2
  38. data/lib/rubocop/cop/gemspec/ordered_dependencies.rb +1 -2
  39. data/lib/rubocop/cop/gemspec/require_mfa.rb +5 -5
  40. data/lib/rubocop/cop/gemspec/ruby_version_globals_usage.rb +12 -7
  41. data/lib/rubocop/cop/internal_affairs/example_heredoc_delimiter.rb +8 -8
  42. data/lib/rubocop/cop/internal_affairs/itblock_handler.rb +69 -0
  43. data/lib/rubocop/cop/internal_affairs/location_exists.rb +28 -2
  44. data/lib/rubocop/cop/internal_affairs/location_line_equality_comparison.rb +1 -0
  45. data/lib/rubocop/cop/internal_affairs/node_matcher_directive.rb +9 -9
  46. data/lib/rubocop/cop/internal_affairs/node_pattern_groups/ast_processor.rb +1 -1
  47. data/lib/rubocop/cop/internal_affairs/node_pattern_groups.rb +3 -1
  48. data/lib/rubocop/cop/internal_affairs/on_send_without_on_csend.rb +1 -1
  49. data/lib/rubocop/cop/internal_affairs/useless_message_assertion.rb +4 -4
  50. data/lib/rubocop/cop/internal_affairs.rb +1 -0
  51. data/lib/rubocop/cop/layout/argument_alignment.rb +2 -2
  52. data/lib/rubocop/cop/layout/array_alignment.rb +1 -1
  53. data/lib/rubocop/cop/layout/begin_end_alignment.rb +1 -1
  54. data/lib/rubocop/cop/layout/case_indentation.rb +3 -1
  55. data/lib/rubocop/cop/layout/class_structure.rb +14 -7
  56. data/lib/rubocop/cop/layout/dot_position.rb +2 -2
  57. data/lib/rubocop/cop/layout/empty_line_after_guard_clause.rb +26 -7
  58. data/lib/rubocop/cop/layout/empty_line_between_defs.rb +31 -13
  59. data/lib/rubocop/cop/layout/empty_lines_after_module_inclusion.rb +2 -2
  60. data/lib/rubocop/cop/layout/empty_lines_around_attribute_accessor.rb +1 -0
  61. data/lib/rubocop/cop/layout/empty_lines_around_block_body.rb +12 -2
  62. data/lib/rubocop/cop/layout/empty_lines_around_class_body.rb +16 -2
  63. data/lib/rubocop/cop/layout/empty_lines_around_module_body.rb +16 -2
  64. data/lib/rubocop/cop/layout/end_alignment.rb +10 -3
  65. data/lib/rubocop/cop/layout/first_argument_indentation.rb +34 -1
  66. data/lib/rubocop/cop/layout/first_array_element_line_break.rb +26 -0
  67. data/lib/rubocop/cop/layout/first_hash_element_indentation.rb +7 -1
  68. data/lib/rubocop/cop/layout/first_hash_element_line_break.rb +25 -25
  69. data/lib/rubocop/cop/layout/hash_alignment.rb +3 -6
  70. data/lib/rubocop/cop/layout/heredoc_argument_closing_parenthesis.rb +2 -2
  71. data/lib/rubocop/cop/layout/heredoc_indentation.rb +33 -3
  72. data/lib/rubocop/cop/layout/indentation_style.rb +1 -1
  73. data/lib/rubocop/cop/layout/indentation_width.rb +123 -7
  74. data/lib/rubocop/cop/layout/line_continuation_spacing.rb +1 -1
  75. data/lib/rubocop/cop/layout/line_length.rb +26 -9
  76. data/lib/rubocop/cop/layout/multiline_array_brace_layout.rb +57 -57
  77. data/lib/rubocop/cop/layout/multiline_assignment_layout.rb +9 -2
  78. data/lib/rubocop/cop/layout/multiline_block_layout.rb +2 -0
  79. data/lib/rubocop/cop/layout/multiline_hash_brace_layout.rb +56 -56
  80. data/lib/rubocop/cop/layout/multiline_method_call_brace_layout.rb +1 -1
  81. data/lib/rubocop/cop/layout/multiline_method_call_indentation.rb +229 -39
  82. data/lib/rubocop/cop/layout/multiline_operation_indentation.rb +8 -4
  83. data/lib/rubocop/cop/layout/parameter_alignment.rb +1 -1
  84. data/lib/rubocop/cop/layout/redundant_line_break.rb +3 -1
  85. data/lib/rubocop/cop/layout/rescue_ensure_alignment.rb +13 -3
  86. data/lib/rubocop/cop/layout/space_after_comma.rb +2 -10
  87. data/lib/rubocop/cop/layout/space_after_semicolon.rb +1 -1
  88. data/lib/rubocop/cop/layout/space_around_block_parameters.rb +1 -1
  89. data/lib/rubocop/cop/layout/space_around_keyword.rb +4 -2
  90. data/lib/rubocop/cop/layout/space_before_brackets.rb +1 -1
  91. data/lib/rubocop/cop/layout/space_in_lambda_literal.rb +9 -8
  92. data/lib/rubocop/cop/layout/trailing_whitespace.rb +1 -1
  93. data/lib/rubocop/cop/lint/ambiguous_block_association.rb +1 -1
  94. data/lib/rubocop/cop/lint/circular_argument_reference.rb +47 -3
  95. data/lib/rubocop/cop/lint/constant_overwritten_in_rescue.rb +4 -3
  96. data/lib/rubocop/cop/lint/constant_reassignment.rb +93 -11
  97. data/lib/rubocop/cop/lint/constant_resolution.rb +6 -6
  98. data/lib/rubocop/cop/lint/cop_directive_syntax.rb +14 -8
  99. data/lib/rubocop/cop/lint/data_define_override.rb +63 -0
  100. data/lib/rubocop/cop/lint/debugger.rb +0 -2
  101. data/lib/rubocop/cop/lint/deprecated_constants.rb +1 -1
  102. data/lib/rubocop/cop/lint/deprecated_open_ssl_constant.rb +4 -1
  103. data/lib/rubocop/cop/lint/duplicate_match_pattern.rb +4 -4
  104. data/lib/rubocop/cop/lint/duplicate_methods.rb +111 -12
  105. data/lib/rubocop/cop/lint/duplicate_regexp_character_class_element.rb +5 -42
  106. data/lib/rubocop/cop/lint/else_layout.rb +19 -0
  107. data/lib/rubocop/cop/lint/empty_block.rb +1 -1
  108. data/lib/rubocop/cop/lint/empty_conditional_body.rb +6 -1
  109. data/lib/rubocop/cop/lint/empty_in_pattern.rb +8 -1
  110. data/lib/rubocop/cop/lint/empty_interpolation.rb +11 -0
  111. data/lib/rubocop/cop/lint/empty_when.rb +8 -1
  112. data/lib/rubocop/cop/lint/erb_new_arguments.rb +1 -1
  113. data/lib/rubocop/cop/lint/float_comparison.rb +1 -1
  114. data/lib/rubocop/cop/lint/interpolation_check.rb +7 -2
  115. data/lib/rubocop/cop/lint/literal_as_condition.rb +5 -1
  116. data/lib/rubocop/cop/lint/literal_in_interpolation.rb +1 -1
  117. data/lib/rubocop/cop/lint/missing_cop_enable_directive.rb +18 -9
  118. data/lib/rubocop/cop/lint/multiple_comparison.rb +2 -2
  119. data/lib/rubocop/cop/lint/next_without_accumulator.rb +2 -0
  120. data/lib/rubocop/cop/lint/no_return_in_begin_end_blocks.rb +4 -0
  121. data/lib/rubocop/cop/lint/non_deterministic_require_order.rb +4 -2
  122. data/lib/rubocop/cop/lint/number_conversion.rb +6 -6
  123. data/lib/rubocop/cop/lint/numbered_parameter_assignment.rb +1 -1
  124. data/lib/rubocop/cop/lint/parentheses_as_grouped_expression.rb +3 -13
  125. data/lib/rubocop/cop/lint/redundant_cop_disable_directive.rb +23 -9
  126. data/lib/rubocop/cop/lint/redundant_cop_enable_directive.rb +2 -11
  127. data/lib/rubocop/cop/lint/redundant_require_statement.rb +4 -2
  128. data/lib/rubocop/cop/lint/redundant_safe_navigation.rb +23 -6
  129. data/lib/rubocop/cop/lint/redundant_splat_expansion.rb +8 -2
  130. data/lib/rubocop/cop/lint/redundant_type_conversion.rb +3 -3
  131. data/lib/rubocop/cop/lint/require_relative_self_path.rb +3 -1
  132. data/lib/rubocop/cop/lint/rescue_exception.rb +1 -4
  133. data/lib/rubocop/cop/lint/safe_navigation_chain.rb +17 -0
  134. data/lib/rubocop/cop/lint/safe_navigation_consistency.rb +7 -1
  135. data/lib/rubocop/cop/lint/self_assignment.rb +15 -6
  136. data/lib/rubocop/cop/lint/shadowed_argument.rb +7 -7
  137. data/lib/rubocop/cop/lint/shadowed_exception.rb +1 -1
  138. data/lib/rubocop/cop/lint/struct_new_override.rb +17 -1
  139. data/lib/rubocop/cop/lint/syntax.rb +25 -1
  140. data/lib/rubocop/cop/lint/to_json.rb +12 -16
  141. data/lib/rubocop/cop/lint/trailing_comma_in_attribute_declaration.rb +1 -0
  142. data/lib/rubocop/cop/lint/unescaped_bracket_in_regexp.rb +1 -1
  143. data/lib/rubocop/cop/lint/unmodified_reduce_accumulator.rb +1 -0
  144. data/lib/rubocop/cop/lint/unreachable_code.rb +7 -5
  145. data/lib/rubocop/cop/lint/unreachable_pattern_branch.rb +113 -0
  146. data/lib/rubocop/cop/lint/unused_method_argument.rb +10 -0
  147. data/lib/rubocop/cop/lint/uri_escape_unescape.rb +2 -0
  148. data/lib/rubocop/cop/lint/useless_assignment.rb +48 -25
  149. data/lib/rubocop/cop/lint/useless_constant_scoping.rb +4 -4
  150. data/lib/rubocop/cop/lint/useless_default_value_argument.rb +2 -0
  151. data/lib/rubocop/cop/lint/useless_or.rb +15 -2
  152. data/lib/rubocop/cop/lint/useless_ruby2_keywords.rb +1 -1
  153. data/lib/rubocop/cop/lint/utils/nil_receiver_checker.rb +37 -11
  154. data/lib/rubocop/cop/lint/void.rb +39 -12
  155. data/lib/rubocop/cop/message_annotator.rb +1 -1
  156. data/lib/rubocop/cop/metrics/block_length.rb +1 -1
  157. data/lib/rubocop/cop/metrics/block_nesting.rb +23 -0
  158. data/lib/rubocop/cop/metrics/method_length.rb +1 -1
  159. data/lib/rubocop/cop/metrics/utils/abc_size_calculator.rb +4 -3
  160. data/lib/rubocop/cop/metrics/utils/iterating_block.rb +1 -1
  161. data/lib/rubocop/cop/migration/department_name.rb +12 -1
  162. data/lib/rubocop/cop/mixin/check_line_breakable.rb +2 -2
  163. data/lib/rubocop/cop/mixin/check_single_line_suitability.rb +4 -6
  164. data/lib/rubocop/cop/mixin/code_length.rb +1 -1
  165. data/lib/rubocop/cop/mixin/configurable_max.rb +6 -5
  166. data/lib/rubocop/cop/mixin/end_keyword_alignment.rb +1 -7
  167. data/lib/rubocop/cop/mixin/hash_shorthand_syntax.rb +5 -5
  168. data/lib/rubocop/cop/mixin/hash_transform_method/autocorrection.rb +63 -0
  169. data/lib/rubocop/cop/mixin/hash_transform_method.rb +10 -60
  170. data/lib/rubocop/cop/mixin/line_length_help.rb +21 -2
  171. data/lib/rubocop/cop/mixin/method_complexity.rb +1 -1
  172. data/lib/rubocop/cop/mixin/multiline_expression_indentation.rb +1 -1
  173. data/lib/rubocop/cop/mixin/multiline_literal_brace_layout.rb +1 -1
  174. data/lib/rubocop/cop/mixin/project_index_help.rb +48 -0
  175. data/lib/rubocop/cop/mixin/space_after_punctuation.rb +5 -4
  176. data/lib/rubocop/cop/mixin/statement_modifier.rb +0 -6
  177. data/lib/rubocop/cop/mixin/trailing_comma.rb +8 -5
  178. data/lib/rubocop/cop/mixin.rb +86 -0
  179. data/lib/rubocop/cop/naming/binary_operator_parameter_name.rb +1 -1
  180. data/lib/rubocop/cop/naming/block_parameter_name.rb +1 -1
  181. data/lib/rubocop/cop/naming/memoized_instance_variable_name.rb +1 -1
  182. data/lib/rubocop/cop/naming/method_name.rb +5 -3
  183. data/lib/rubocop/cop/naming/predicate_method.rb +32 -8
  184. data/lib/rubocop/cop/naming/predicate_prefix.rb +12 -12
  185. data/lib/rubocop/cop/naming/rescued_exceptions_variable_name.rb +1 -1
  186. data/lib/rubocop/cop/offense.rb +17 -1
  187. data/lib/rubocop/cop/registry.rb +62 -38
  188. data/lib/rubocop/cop/security/eval.rb +15 -2
  189. data/lib/rubocop/cop/security/io_methods.rb +1 -1
  190. data/lib/rubocop/cop/security/json_load.rb +33 -11
  191. data/lib/rubocop/cop/style/access_modifier_declarations.rb +15 -4
  192. data/lib/rubocop/cop/style/accessor_grouping.rb +4 -2
  193. data/lib/rubocop/cop/style/alias.rb +14 -2
  194. data/lib/rubocop/cop/style/and_or.rb +1 -0
  195. data/lib/rubocop/cop/style/arguments_forwarding.rb +25 -7
  196. data/lib/rubocop/cop/style/array_intersect.rb +46 -12
  197. data/lib/rubocop/cop/style/array_intersect_with_single_element.rb +47 -0
  198. data/lib/rubocop/cop/style/array_join.rb +4 -2
  199. data/lib/rubocop/cop/style/ascii_comments.rb +6 -3
  200. data/lib/rubocop/cop/style/attr.rb +5 -2
  201. data/lib/rubocop/cop/style/bare_percent_literals.rb +4 -3
  202. data/lib/rubocop/cop/style/begin_block.rb +3 -1
  203. data/lib/rubocop/cop/style/bitwise_predicate.rb +8 -1
  204. data/lib/rubocop/cop/style/block_delimiters.rb +27 -34
  205. data/lib/rubocop/cop/style/case_equality.rb +15 -13
  206. data/lib/rubocop/cop/style/character_literal.rb +2 -2
  207. data/lib/rubocop/cop/style/class_and_module_children.rb +19 -2
  208. data/lib/rubocop/cop/style/collection_compact.rb +36 -16
  209. data/lib/rubocop/cop/style/colon_method_call.rb +3 -1
  210. data/lib/rubocop/cop/style/concat_array_literals.rb +2 -0
  211. data/lib/rubocop/cop/style/conditional_assignment.rb +8 -18
  212. data/lib/rubocop/cop/style/constant_visibility.rb +17 -12
  213. data/lib/rubocop/cop/style/copyright.rb +22 -11
  214. data/lib/rubocop/cop/style/date_time.rb +2 -2
  215. data/lib/rubocop/cop/style/disable_cops_within_source_code_directive.rb +1 -1
  216. data/lib/rubocop/cop/style/document_dynamic_eval_definition.rb +6 -1
  217. data/lib/rubocop/cop/style/documentation.rb +6 -6
  218. data/lib/rubocop/cop/style/documentation_method.rb +8 -8
  219. data/lib/rubocop/cop/style/double_negation.rb +1 -1
  220. data/lib/rubocop/cop/style/each_for_simple_loop.rb +1 -1
  221. data/lib/rubocop/cop/style/each_with_object.rb +2 -0
  222. data/lib/rubocop/cop/style/empty_block_parameter.rb +1 -1
  223. data/lib/rubocop/cop/style/empty_class_definition.rb +119 -0
  224. data/lib/rubocop/cop/style/empty_lambda_parameter.rb +1 -1
  225. data/lib/rubocop/cop/style/empty_method.rb +0 -6
  226. data/lib/rubocop/cop/style/encoding.rb +7 -1
  227. data/lib/rubocop/cop/style/end_block.rb +3 -1
  228. data/lib/rubocop/cop/style/endless_method.rb +23 -5
  229. data/lib/rubocop/cop/style/explicit_block_argument.rb +1 -1
  230. data/lib/rubocop/cop/style/file_open.rb +84 -0
  231. data/lib/rubocop/cop/style/file_write.rb +18 -16
  232. data/lib/rubocop/cop/style/float_division.rb +15 -1
  233. data/lib/rubocop/cop/style/for.rb +3 -0
  234. data/lib/rubocop/cop/style/format_string.rb +4 -3
  235. data/lib/rubocop/cop/style/format_string_token.rb +49 -5
  236. data/lib/rubocop/cop/style/global_vars.rb +5 -2
  237. data/lib/rubocop/cop/style/guard_clause.rb +27 -22
  238. data/lib/rubocop/cop/style/hash_as_last_array_item.rb +27 -9
  239. data/lib/rubocop/cop/style/hash_conversion.rb +1 -1
  240. data/lib/rubocop/cop/style/hash_lookup_method.rb +106 -0
  241. data/lib/rubocop/cop/style/hash_syntax.rb +1 -1
  242. data/lib/rubocop/cop/style/hash_transform_keys.rb +17 -7
  243. data/lib/rubocop/cop/style/hash_transform_values.rb +17 -7
  244. data/lib/rubocop/cop/style/if_inside_else.rb +16 -7
  245. data/lib/rubocop/cop/style/if_unless_modifier.rb +57 -17
  246. data/lib/rubocop/cop/style/if_unless_modifier_of_if_unless.rb +12 -12
  247. data/lib/rubocop/cop/style/if_with_boolean_literal_branches.rb +4 -1
  248. data/lib/rubocop/cop/style/if_with_semicolon.rb +7 -5
  249. data/lib/rubocop/cop/style/infinite_loop.rb +1 -1
  250. data/lib/rubocop/cop/style/inline_comment.rb +4 -1
  251. data/lib/rubocop/cop/style/ip_addresses.rb +1 -2
  252. data/lib/rubocop/cop/style/lambda_call.rb +8 -8
  253. data/lib/rubocop/cop/style/magic_comment_format.rb +3 -3
  254. data/lib/rubocop/cop/style/map_join.rb +123 -0
  255. data/lib/rubocop/cop/style/method_call_with_args_parentheses/require_parentheses.rb +15 -2
  256. data/lib/rubocop/cop/style/method_call_with_args_parentheses.rb +17 -4
  257. data/lib/rubocop/cop/style/method_def_parentheses.rb +2 -4
  258. data/lib/rubocop/cop/style/min_max_comparison.rb +1 -1
  259. data/lib/rubocop/cop/style/module_member_existence_check.rb +110 -0
  260. data/lib/rubocop/cop/style/multiline_if_then.rb +4 -4
  261. data/lib/rubocop/cop/style/multiline_method_signature.rb +2 -4
  262. data/lib/rubocop/cop/style/mutable_constant.rb +1 -1
  263. data/lib/rubocop/cop/style/negative_array_index.rb +220 -0
  264. data/lib/rubocop/cop/style/nil_comparison.rb +11 -10
  265. data/lib/rubocop/cop/style/nil_lambda.rb +1 -1
  266. data/lib/rubocop/cop/style/non_nil_check.rb +5 -11
  267. data/lib/rubocop/cop/style/not.rb +2 -0
  268. data/lib/rubocop/cop/style/numeric_literals.rb +3 -2
  269. data/lib/rubocop/cop/style/one_class_per_file.rb +115 -0
  270. data/lib/rubocop/cop/style/one_line_conditional.rb +21 -12
  271. data/lib/rubocop/cop/style/operator_method_call.rb +11 -2
  272. data/lib/rubocop/cop/style/parallel_assignment.rb +6 -2
  273. data/lib/rubocop/cop/style/partition_instead_of_double_select.rb +270 -0
  274. data/lib/rubocop/cop/style/percent_literal_delimiters.rb +2 -0
  275. data/lib/rubocop/cop/style/predicate_with_kind.rb +84 -0
  276. data/lib/rubocop/cop/style/preferred_hash_methods.rb +12 -12
  277. data/lib/rubocop/cop/style/proc.rb +3 -2
  278. data/lib/rubocop/cop/style/raise_args.rb +1 -1
  279. data/lib/rubocop/cop/style/reduce_to_hash.rb +200 -0
  280. data/lib/rubocop/cop/style/redundant_argument.rb +2 -0
  281. data/lib/rubocop/cop/style/redundant_array_constructor.rb +2 -2
  282. data/lib/rubocop/cop/style/redundant_begin.rb +37 -3
  283. data/lib/rubocop/cop/style/redundant_condition.rb +6 -3
  284. data/lib/rubocop/cop/style/redundant_constant_base.rb +5 -5
  285. data/lib/rubocop/cop/style/redundant_each.rb +3 -3
  286. data/lib/rubocop/cop/style/redundant_exception.rb +1 -1
  287. data/lib/rubocop/cop/style/redundant_fetch_block.rb +1 -1
  288. data/lib/rubocop/cop/style/redundant_format.rb +26 -5
  289. data/lib/rubocop/cop/style/redundant_interpolation.rb +11 -2
  290. data/lib/rubocop/cop/style/redundant_interpolation_unfreeze.rb +26 -10
  291. data/lib/rubocop/cop/style/redundant_line_continuation.rb +16 -0
  292. data/lib/rubocop/cop/style/redundant_min_max_by.rb +93 -0
  293. data/lib/rubocop/cop/style/redundant_parentheses.rb +36 -30
  294. data/lib/rubocop/cop/style/redundant_percent_q.rb +5 -3
  295. data/lib/rubocop/cop/style/redundant_regexp_argument.rb +9 -0
  296. data/lib/rubocop/cop/style/redundant_regexp_constructor.rb +2 -2
  297. data/lib/rubocop/cop/style/redundant_regexp_escape.rb +8 -0
  298. data/lib/rubocop/cop/style/redundant_return.rb +3 -1
  299. data/lib/rubocop/cop/style/redundant_self.rb +2 -2
  300. data/lib/rubocop/cop/style/redundant_self_assignment_branch.rb +0 -5
  301. data/lib/rubocop/cop/style/redundant_sort.rb +7 -7
  302. data/lib/rubocop/cop/style/redundant_struct_keyword_init.rb +114 -0
  303. data/lib/rubocop/cop/style/regexp_literal.rb +31 -2
  304. data/lib/rubocop/cop/style/rescue_modifier.rb +3 -3
  305. data/lib/rubocop/cop/style/reverse_find.rb +51 -0
  306. data/lib/rubocop/cop/style/safe_navigation.rb +25 -8
  307. data/lib/rubocop/cop/style/select_by_kind.rb +158 -0
  308. data/lib/rubocop/cop/style/select_by_range.rb +197 -0
  309. data/lib/rubocop/cop/style/select_by_regexp.rb +51 -21
  310. data/lib/rubocop/cop/style/self_assignment.rb +1 -1
  311. data/lib/rubocop/cop/style/semicolon.rb +25 -7
  312. data/lib/rubocop/cop/style/single_line_block_params.rb +2 -2
  313. data/lib/rubocop/cop/style/single_line_do_end_block.rb +1 -1
  314. data/lib/rubocop/cop/style/single_line_methods.rb +3 -1
  315. data/lib/rubocop/cop/style/sole_nested_conditional.rb +12 -3
  316. data/lib/rubocop/cop/style/special_global_vars.rb +6 -1
  317. data/lib/rubocop/cop/style/string_concatenation.rb +17 -13
  318. data/lib/rubocop/cop/style/struct_inheritance.rb +13 -0
  319. data/lib/rubocop/cop/style/super_arguments.rb +2 -2
  320. data/lib/rubocop/cop/style/symbol_array.rb +1 -1
  321. data/lib/rubocop/cop/style/symbol_proc.rb +7 -6
  322. data/lib/rubocop/cop/style/tally_method.rb +181 -0
  323. data/lib/rubocop/cop/style/top_level_method_definition.rb +2 -2
  324. data/lib/rubocop/cop/style/trailing_comma_in_arguments.rb +45 -0
  325. data/lib/rubocop/cop/style/trailing_comma_in_block_args.rb +1 -1
  326. data/lib/rubocop/cop/style/trailing_method_end_statement.rb +1 -0
  327. data/lib/rubocop/cop/style/trailing_underscore_variable.rb +11 -11
  328. data/lib/rubocop/cop/style/unless_else.rb +10 -9
  329. data/lib/rubocop/cop/style/unless_logical_operators.rb +3 -3
  330. data/lib/rubocop/cop/style/while_until_modifier.rb +16 -0
  331. data/lib/rubocop/cop/style/yoda_condition.rb +1 -1
  332. data/lib/rubocop/cop/style/yoda_expression.rb +1 -1
  333. data/lib/rubocop/cop/team.rb +87 -36
  334. data/lib/rubocop/cop/util.rb +2 -3
  335. data/lib/rubocop/cop/utils/format_string.rb +10 -0
  336. data/lib/rubocop/cop/variable_force/branch.rb +30 -6
  337. data/lib/rubocop/cop/variable_force/variable.rb +1 -1
  338. data/lib/rubocop/cop/variable_force.rb +9 -7
  339. data/lib/rubocop/cops_documentation_generator.rb +4 -4
  340. data/lib/rubocop/directive_comment.rb +48 -4
  341. data/lib/rubocop/file_patterns.rb +9 -1
  342. data/lib/rubocop/formatter/clang_style_formatter.rb +5 -2
  343. data/lib/rubocop/formatter/disabled_config_formatter.rb +24 -7
  344. data/lib/rubocop/formatter/formatter_set.rb +2 -2
  345. data/lib/rubocop/formatter/junit_formatter.rb +1 -1
  346. data/lib/rubocop/formatter/simple_text_formatter.rb +0 -2
  347. data/lib/rubocop/formatter/tap_formatter.rb +5 -2
  348. data/lib/rubocop/formatter/worst_offenders_formatter.rb +1 -1
  349. data/lib/rubocop/formatter.rb +22 -21
  350. data/lib/rubocop/lsp/diagnostic.rb +18 -33
  351. data/lib/rubocop/lsp/disable_comment_edits.rb +135 -0
  352. data/lib/rubocop/lsp/routes.rb +43 -7
  353. data/lib/rubocop/lsp/runtime.rb +13 -4
  354. data/lib/rubocop/lsp/stdin_runner.rb +8 -17
  355. data/lib/rubocop/magic_comment.rb +20 -0
  356. data/lib/rubocop/mcp/server.rb +200 -0
  357. data/lib/rubocop/options.rb +35 -4
  358. data/lib/rubocop/path_util.rb +14 -2
  359. data/lib/rubocop/plugin/loader.rb +1 -1
  360. data/lib/rubocop/project_index_loader.rb +66 -0
  361. data/lib/rubocop/rake_task.rb +1 -1
  362. data/lib/rubocop/remote_config.rb +10 -8
  363. data/lib/rubocop/result_cache.rb +61 -38
  364. data/lib/rubocop/rspec/cop_helper.rb +8 -0
  365. data/lib/rubocop/rspec/shared_contexts.rb +39 -5
  366. data/lib/rubocop/rspec/support.rb +2 -1
  367. data/lib/rubocop/runner.rb +134 -57
  368. data/lib/rubocop/server/cache.rb +6 -29
  369. data/lib/rubocop/server/core.rb +2 -0
  370. data/lib/rubocop/target_finder.rb +17 -10
  371. data/lib/rubocop/target_ruby.rb +31 -14
  372. data/lib/rubocop/version.rb +21 -3
  373. data/lib/rubocop.rb +28 -96
  374. data/lib/ruby_lsp/rubocop/addon.rb +23 -8
  375. data/lib/ruby_lsp/rubocop/runtime_adapter.rb +49 -15
  376. metadata +38 -9
@@ -0,0 +1,135 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module LSP
5
+ # Builds LSP text edits for rubocop:disable comments.
6
+ # @api private
7
+ class DisableCommentEdits
8
+ def initialize(offense:, document_encoding:, processed_source:)
9
+ @offense = offense
10
+ @document_encoding = document_encoding
11
+ @processed_source = processed_source
12
+ end
13
+
14
+ def edits
15
+ literal_range = multiline_literal_range
16
+ return block_disable_comments(literal_range) if literal_range
17
+
18
+ inline_disable_comment
19
+ end
20
+
21
+ private
22
+
23
+ def inline_disable_comment
24
+ eol = position(@offense.line - 1, @offense.source_line.length, @offense.source_line)
25
+ [text_edit(eol, inline_comment_text)]
26
+ end
27
+
28
+ def inline_comment_text
29
+ if @offense.source_line.include?(' # rubocop:disable ')
30
+ ",#{@offense.cop_name}"
31
+ else
32
+ " # rubocop:disable #{@offense.cop_name}"
33
+ end
34
+ end
35
+
36
+ def block_disable_comments(range)
37
+ full_line_range = range_by_lines(range)
38
+ leading_whitespace = full_line_range.source_line[/^\s*/].to_s
39
+ [
40
+ disable_edit(full_line_range.first_line, leading_whitespace),
41
+ enable_edit(full_line_range, leading_whitespace)
42
+ ]
43
+ end
44
+
45
+ def disable_edit(first_line, leading_whitespace)
46
+ position = position(first_line - 1, 0, '')
47
+ text_edit(position, "#{leading_whitespace}# rubocop:disable #{@offense.cop_name}\n")
48
+ end
49
+
50
+ def enable_edit(full_line_range, leading_whitespace)
51
+ last_line = full_line_range.last_line
52
+ last_line_text = full_line_range.source_buffer.source_line(last_line)
53
+ position = position(last_line - 1, last_line_text.length, last_line_text)
54
+ text_edit(position, "\n#{leading_whitespace}# rubocop:enable #{@offense.cop_name}")
55
+ end
56
+
57
+ def text_edit(position, new_text)
58
+ range = LanguageServer::Protocol::Interface::Range.new(start: position, end: position)
59
+ LanguageServer::Protocol::Interface::TextEdit.new(range: range, new_text: new_text)
60
+ end
61
+
62
+ def position(line, utf8_index, line_text)
63
+ LanguageServer::Protocol::Interface::Position.new(
64
+ line: line,
65
+ character: position_character(utf8_index, line_text)
66
+ )
67
+ end
68
+
69
+ def position_character(utf8_index, line_text)
70
+ str = line_text[0, utf8_index]
71
+ if @document_encoding == Encoding::UTF_16LE || @document_encoding.nil?
72
+ str.length + str.b.count("\xf0-\xff".b)
73
+ else
74
+ str.length
75
+ end
76
+ end
77
+
78
+ def multiline_literal_range
79
+ return unless @processed_source&.ast
80
+
81
+ offense_range = @offense.location
82
+ multiline_ranges&.find do |range|
83
+ eol_comment_would_be_inside_literal?(offense_range, range)
84
+ end
85
+ end
86
+
87
+ def multiline_ranges
88
+ @processed_source.ast.each_node.filter_map do |node|
89
+ if surrounding_heredoc?(node)
90
+ heredoc_range(node)
91
+ elsif string_continuation?(node)
92
+ range_by_lines(node.source_range)
93
+ elsif surrounding_percent_array?(node) || multiline_string?(node)
94
+ node.source_range
95
+ end
96
+ end
97
+ end
98
+
99
+ def eol_comment_would_be_inside_literal?(offense_range, literal_range)
100
+ offense_line = offense_range.line
101
+ offense_line >= literal_range.first_line && offense_line < literal_range.last_line
102
+ end
103
+
104
+ def surrounding_heredoc?(node)
105
+ node.any_str_type? && node.heredoc?
106
+ end
107
+
108
+ def heredoc_range(node)
109
+ node.source_range.join(node.loc.heredoc_end)
110
+ end
111
+
112
+ def surrounding_percent_array?(node)
113
+ node.array_type? && node.percent_literal?
114
+ end
115
+
116
+ def string_continuation?(node)
117
+ node.any_str_type? && node.source.match?(/\\\s*$/)
118
+ end
119
+
120
+ def multiline_string?(node)
121
+ node.dstr_type? && node.multiline?
122
+ end
123
+
124
+ def range_by_lines(range)
125
+ begin_of_first_line = range.begin_pos - range.column
126
+
127
+ last_line = range.source_buffer.source_line(range.last_line)
128
+ last_line_offset = last_line.length - range.last_column
129
+ end_of_last_line = range.end_pos + last_line_offset
130
+
131
+ Parser::Source::Range.new(range.source_buffer, begin_of_first_line, end_of_last_line)
132
+ end
133
+ end
134
+ end
135
+ end
@@ -51,7 +51,7 @@ module RuboCop
51
51
  capabilities: LanguageServer::Protocol::Interface::ServerCapabilities.new(
52
52
  document_formatting_provider: true,
53
53
  text_document_sync: LanguageServer::Protocol::Interface::TextDocumentSyncOptions.new(
54
- change: LanguageServer::Protocol::Constant::TextDocumentSyncKind::FULL,
54
+ change: LanguageServer::Protocol::Constant::TextDocumentSyncKind::INCREMENTAL,
55
55
  open_close: true
56
56
  )
57
57
  )
@@ -61,9 +61,16 @@ module RuboCop
61
61
 
62
62
  handle 'initialized' do |_request|
63
63
  version = RuboCop::Version::STRING
64
- yjit = Object.const_defined?('RubyVM::YJIT') && RubyVM::YJIT.enabled? ? ' +YJIT' : ''
65
-
66
- Logger.log("RuboCop #{version} language server#{yjit} initialized, PID #{Process.pid}")
64
+ # Only one JIT can be enabled at the same time, since YJIT and ZJIT are mutually exclusive.
65
+ jit = if Object.const_defined?('RubyVM::YJIT') && RubyVM::YJIT.enabled?
66
+ '+YJIT'
67
+ elsif Object.const_defined?('RubyVM::ZJIT') && RubyVM::ZJIT.enabled?
68
+ '+ZJIT'
69
+ else
70
+ ''
71
+ end
72
+
73
+ Logger.log("RuboCop #{version} language server#{jit} initialized, PID #{Process.pid}")
67
74
  end
68
75
 
69
76
  handle 'shutdown' do |request|
@@ -76,7 +83,12 @@ module RuboCop
76
83
 
77
84
  handle 'textDocument/didChange' do |request|
78
85
  params = request[:params]
79
- result = diagnostic(params[:textDocument][:uri], params[:contentChanges][0][:text])
86
+ file_uri = params[:textDocument][:uri]
87
+ text = @text_cache[file_uri]
88
+ params[:contentChanges].each do |content|
89
+ text = change_text(text, content[:text], content[:range])
90
+ end
91
+ result = diagnostic(file_uri, text)
80
92
  @server.write(result)
81
93
  end
82
94
 
@@ -198,13 +210,13 @@ module RuboCop
198
210
 
199
211
  return [] if new_text == text
200
212
 
201
- [
213
+ [{
202
214
  newText: new_text,
203
215
  range: {
204
216
  start: { line: 0, character: 0 },
205
217
  end: { line: text.count("\n") + 1, character: 0 }
206
218
  }
207
- ]
219
+ }]
208
220
  end
209
221
 
210
222
  def diagnostic(file_uri, text)
@@ -219,6 +231,30 @@ module RuboCop
219
231
  }
220
232
  end
221
233
 
234
+ def change_text(orig_text, text, range)
235
+ return text unless range
236
+
237
+ start_pos = text_pos(orig_text, range[:start])
238
+ end_pos = text_pos(orig_text, range[:end])
239
+ text_bin = orig_text.b
240
+ text_bin[start_pos...end_pos] = text.b
241
+ text_bin.force_encoding(orig_text.encoding)
242
+ end
243
+
244
+ def text_pos(text, range)
245
+ line = range[:line]
246
+ char = range[:character]
247
+ pos = 0
248
+ text.each_line.with_index do |l, i|
249
+ if i == line
250
+ pos += l.encode('utf-16be').b[0, char * 2].encode('utf-8', 'utf-16be').bytesize
251
+ return pos
252
+ end
253
+ pos += l.bytesize
254
+ end
255
+ pos
256
+ end
257
+
222
258
  def convert_file_uri_to_path(uri)
223
259
  URI.decode_www_form_component(uri.delete_prefix('file://'))
224
260
  end
@@ -23,7 +23,6 @@ module RuboCop
23
23
  RuboCop::LSP.enable
24
24
 
25
25
  @runner = RuboCop::Lsp::StdinRunner.new(config_store)
26
- @cop_registry = RuboCop::Cop::Registry.global.to_h
27
26
 
28
27
  @safe_autocorrect = true
29
28
  @lint_mode = false
@@ -49,15 +48,25 @@ module RuboCop
49
48
  diagnostic_options[:only] = config_only_options if @lint_mode || @layout_mode
50
49
 
51
50
  @runner.run(path, text, diagnostic_options, prism_result: prism_result)
51
+ processed_source = @runner.processed_source
52
+ config = @runner.config_for_working_directory
52
53
  @runner.offenses.map do |offense|
53
- Diagnostic.new(
54
- document_encoding, offense, path, @cop_registry[offense.cop_name]&.first
55
- ).to_lsp_diagnostic(@runner.config_for_working_directory)
54
+ build_diagnostic(offense, path, document_encoding, processed_source, config)
56
55
  end
57
56
  end
58
57
 
59
58
  private
60
59
 
60
+ def build_diagnostic(offense, path, document_encoding, processed_source, config)
61
+ Diagnostic.new(
62
+ document_encoding,
63
+ offense,
64
+ path,
65
+ RuboCop::Cop::Registry.global.find_by_cop_name(offense.cop_name),
66
+ processed_source
67
+ ).to_lsp_diagnostic(config)
68
+ end
69
+
61
70
  def config_only_options
62
71
  only_options = []
63
72
  only_options << 'Lint' if @lint_mode
@@ -17,7 +17,7 @@ module RuboCop
17
17
  class StdinRunner < RuboCop::Runner
18
18
  class ConfigurationError < StandardError; end
19
19
 
20
- attr_reader :offenses, :config_for_working_directory
20
+ attr_reader :offenses, :config_for_working_directory, :processed_source
21
21
 
22
22
  DEFAULT_RUBOCOP_OPTIONS = {
23
23
  stderr: true,
@@ -40,12 +40,12 @@ module RuboCop
40
40
  super(@options, config_store)
41
41
  end
42
42
 
43
- # rubocop:disable Metrics/MethodLength
44
43
  def run(path, contents, options, prism_result: nil)
45
44
  @options = options.merge(DEFAULT_RUBOCOP_OPTIONS)
46
45
  @options[:stdin] = contents
47
46
 
48
47
  @prism_result = prism_result
48
+ @processed_source = nil
49
49
 
50
50
  @offenses = []
51
51
  @warnings = []
@@ -54,22 +54,7 @@ module RuboCop
54
54
  super([path])
55
55
 
56
56
  raise Interrupt if aborting?
57
- rescue RuboCop::Runner::InfiniteCorrectionLoop => e
58
- if defined?(::RubyLsp::Requests::Formatting::Error)
59
- raise ::RubyLsp::Requests::Formatting::Error, e.message
60
- end
61
-
62
- raise e
63
- rescue RuboCop::ValidationError => e
64
- raise ConfigurationError, e.message
65
- rescue StandardError => e
66
- if defined?(::RubyLsp::Requests::Formatting::Error)
67
- raise ::RubyLsp::Requests::Support::InternalRuboCopError, e
68
- end
69
-
70
- raise e
71
57
  end
72
- # rubocop:enable Metrics/MethodLength
73
58
 
74
59
  def formatted_source
75
60
  @options[:stdin]
@@ -80,6 +65,12 @@ module RuboCop
80
65
  def file_finished(_file, offenses)
81
66
  @offenses = offenses
82
67
  end
68
+
69
+ def do_inspection_loop(file)
70
+ source, offenses = super
71
+ @processed_source = source
72
+ [source, offenses]
73
+ end
83
74
  end
84
75
  end
85
76
  end
@@ -11,6 +11,7 @@ module RuboCop
11
11
  KEYWORDS = {
12
12
  encoding: '(?:en)?coding',
13
13
  frozen_string_literal: 'frozen[_-]string[_-]literal',
14
+ rbs_inline: 'rbs_inline',
14
15
  shareable_constant_value: 'shareable[_-]constant[_-]value',
15
16
  typed: 'typed'
16
17
  }.freeze
@@ -36,6 +37,7 @@ module RuboCop
36
37
  def any?
37
38
  frozen_string_literal_specified? ||
38
39
  encoding_specified? ||
40
+ rbs_inline_specified? ||
39
41
  shareable_constant_value_specified? ||
40
42
  typed_specified?
41
43
  end
@@ -60,6 +62,10 @@ module RuboCop
60
62
  [true, false].include?(frozen_string_literal)
61
63
  end
62
64
 
65
+ def valid_rbs_inline_value?
66
+ %w[enabled disabled].include?(extract_rbs_inline_value)
67
+ end
68
+
63
69
  def valid_shareable_constant_value?
64
70
  %w[none literal experimental_everything experimental_copy].include?(shareable_constant_value)
65
71
  end
@@ -105,6 +111,10 @@ module RuboCop
105
111
  specified?(encoding)
106
112
  end
107
113
 
114
+ def rbs_inline_specified?
115
+ valid_rbs_inline_value?
116
+ end
117
+
108
118
  # Was the Sorbet `typed` sigil specified?
109
119
  #
110
120
  # @return [Boolean]
@@ -203,6 +213,9 @@ module RuboCop
203
213
  match(KEYWORDS[:frozen_string_literal])
204
214
  end
205
215
 
216
+ # Emacs comments cannot specify RBS::inline behavior.
217
+ def extract_rbs_inline_value; end
218
+
206
219
  def extract_shareable_constant_value
207
220
  match(KEYWORDS[:shareable_constant_value])
208
221
  end
@@ -242,6 +255,9 @@ module RuboCop
242
255
  # Vim comments cannot specify frozen string literal behavior.
243
256
  def frozen_string_literal; end
244
257
 
258
+ # Vim comments cannot specify RBS::inline behavior.
259
+ def extract_rbs_inline_value; end
260
+
245
261
  # Vim comments cannot specify shareable constant values behavior.
246
262
  def shareable_constant_value; end
247
263
 
@@ -296,6 +312,10 @@ module RuboCop
296
312
  extract(/\A\s*#\s*#{KEYWORDS[:frozen_string_literal]}:\s*#{TOKEN}\s*\z/io)
297
313
  end
298
314
 
315
+ def extract_rbs_inline_value
316
+ extract(/\A\s*#\s*#{KEYWORDS[:rbs_inline]}:\s*#{TOKEN}\s*\z/io)
317
+ end
318
+
299
319
  def extract_shareable_constant_value
300
320
  extract(/\A\s*#\s*#{KEYWORDS[:shareable_constant_value]}:\s*#{TOKEN}\s*\z/io)
301
321
  end
@@ -0,0 +1,200 @@
1
+ # frozen_string_literal: true
2
+
3
+ begin
4
+ require 'mcp'
5
+
6
+ required_mcp_version = '0.6.0'
7
+
8
+ if Gem::Version.new(required_mcp_version) > Gem::Version.new(MCP::VERSION)
9
+ # While `mcp` is not a runtime dependency, users may have an outdated version installed.
10
+ warn <<~MESSAGE
11
+ Error: `mcp` gem version #{MCP::VERSION} was loaded, but `rubocop --mcp` requires #{required_mcp_version}.
12
+ - If you're using Bundler and don't yet have `gem 'mcp'` as a dependency, add it now.
13
+ - If you're using Bundler and already have `gem 'mcp'` as a dependency, update it to the most recent version.
14
+ - If you don't use Bundler, run `gem update mcp`.
15
+ MESSAGE
16
+ exit!
17
+ end
18
+ rescue LoadError => e
19
+ raise unless e.path == 'mcp'
20
+
21
+ warn <<~MESSAGE
22
+ Error: Unable to load `mcp` gem. Add `gem 'mcp', '~> 0.6'` to your Gemfile, or run `gem install mcp`.
23
+ MESSAGE
24
+
25
+ exit!
26
+ end
27
+
28
+ require_relative '../lsp'
29
+ require_relative '../lsp/runtime'
30
+
31
+ module RuboCop
32
+ module MCP
33
+ # RuboCop MCP Server.
34
+ # @api private
35
+ class Server
36
+ def initialize(config_store)
37
+ @config_store = config_store
38
+ @runtime = RuboCop::LSP::Runtime.new(@config_store)
39
+ @options = {}
40
+ end
41
+
42
+ def start
43
+ # No `protocol_version` is specified because draft feature by default can be used.
44
+ server = ::MCP::Server.new(
45
+ name: 'rubocop_mcp_server',
46
+ version: RuboCop::Version::STRING,
47
+ tools: [inspection_tool, autocorrection_tool]
48
+ )
49
+
50
+ ::MCP::Server::Transports::StdioTransport.new(server).open
51
+ end
52
+
53
+ private
54
+
55
+ def inspection_tool
56
+ build_tool(
57
+ name: 'rubocop_inspection',
58
+ description: 'Inspect Ruby code for offenses. ' \
59
+ 'Provide `source_code` to check inline code or `path` to check files.',
60
+ title: "RuboCop's inspection",
61
+ destructive_hint: false,
62
+ idempotent_hint: true,
63
+ read_only_hint: true,
64
+ safety_required: false
65
+ ) do |path, source_code|
66
+ run_inspection(path, source_code)
67
+ end
68
+ end
69
+
70
+ def autocorrection_tool
71
+ build_tool(
72
+ name: 'rubocop_autocorrection',
73
+ description: 'Autocorrect RuboCop offenses in Ruby code. ' \
74
+ 'Provide `source_code` to correct inline code or `path` to correct files. ' \
75
+ 'Set `safety` to false to include unsafe corrections.',
76
+ title: "RuboCop's autocorrection",
77
+ destructive_hint: true,
78
+ idempotent_hint: false,
79
+ read_only_hint: false,
80
+ safety_required: true
81
+ ) do |path, source_code, safety|
82
+ run_autocorrection(path, source_code, safety)
83
+ end
84
+ end
85
+
86
+ def run_inspection(path, source_code)
87
+ if source_code
88
+ offenses = @runtime.offenses(path || 'example.rb', source_code, source_code.encoding)
89
+ offenses.to_json
90
+ else
91
+ process_files(path, filter_empty: true) do |file, source|
92
+ offenses = @runtime.offenses(file, source, source.encoding)
93
+
94
+ { path: PathUtil.relative_path(file), offenses: offenses }
95
+ end
96
+ end
97
+ end
98
+
99
+ def run_autocorrection(path, source_code, safety)
100
+ command = safety ? 'rubocop.formatAutocorrects' : 'rubocop.formatAutocorrectsAll'
101
+
102
+ if source_code
103
+ @runtime.format(path || 'example.rb', source_code, command: command).tap do |corrected|
104
+ write_file(path, corrected) if path
105
+ end
106
+ else
107
+ process_files(path) do |file, source|
108
+ @runtime.format(file, source, command: command).then do |corrected|
109
+ write_file(file, corrected)
110
+
111
+ { path: PathUtil.relative_path(file), corrected: source != corrected }
112
+ end
113
+ end
114
+ end
115
+ end
116
+
117
+ def process_files(path, filter_empty: false)
118
+ target_finder = RuboCop::TargetFinder.new(@config_store, @options)
119
+ target_files = target_finder.find(path ? [path] : [], :only_recognized_file_types)
120
+ all_files = target_files.map { |file| yield(file, read_file(file)) }
121
+ files = filter_empty ? all_files.reject { |f| f[:offenses]&.empty? } : all_files
122
+
123
+ { files: files, summary: build_summary(target_files, all_files) }.to_json
124
+ end
125
+
126
+ def read_file(file)
127
+ config = @config_store.for_file(file)
128
+ RuboCop::ProcessedSource.from_file(
129
+ file, config.target_ruby_version, parser_engine: config.parser_engine
130
+ ).raw_source
131
+ rescue Errno::ENOENT
132
+ raise RuboCop::Error, "No such file or directory: #{file}"
133
+ end
134
+
135
+ def write_file(file, content)
136
+ File.write(file, content)
137
+ rescue Errno::EACCES
138
+ raise RuboCop::Error, "Permission denied: #{file}"
139
+ rescue Errno::ENOSPC
140
+ raise RuboCop::Error, "No space left on device: #{file}"
141
+ rescue Errno::EROFS
142
+ raise RuboCop::Error, "Read-only file system: #{file}"
143
+ end
144
+
145
+ # NOTE: It is useful for RuboCop's result summary to be shown in the LLM's responses
146
+ # during interactions, so the summary is returned in a form that is easy for the LLM
147
+ # to reason about. Since LLM execution is non-deterministic, it is also sensible to
148
+ # compute the summary deterministically at this stage.
149
+ def build_summary(target_files, files)
150
+ summary = { target_file_count: target_files.count }
151
+ if files.first&.key?(:offenses)
152
+ summary[:offense_count] = files.sum { |f| f[:offenses].size }
153
+ else
154
+ summary[:corrected_file_count] = files.count { |f| f[:corrected] }
155
+ end
156
+ summary
157
+ end
158
+
159
+ # rubocop:disable Metrics/MethodLength, Metrics/ParameterLists
160
+ def build_tool(
161
+ name:, description:,
162
+ title:, destructive_hint:, idempotent_hint:, read_only_hint:, safety_required:
163
+ )
164
+ if safety_required
165
+ safety_property = { safety: { type: 'boolean' } }
166
+ required = ['safety']
167
+ else
168
+ safety_property = {}
169
+ required = nil
170
+ end
171
+
172
+ ::MCP::Tool.define(
173
+ name: name,
174
+ description: description,
175
+ input_schema: {
176
+ properties: {
177
+ path: { type: 'string' },
178
+ source_code: { type: 'string' }
179
+ }.merge(safety_property),
180
+ required: required
181
+ }.compact,
182
+ annotations: {
183
+ title: title,
184
+ destructive_hint: destructive_hint,
185
+ idempotent_hint: idempotent_hint,
186
+ open_world_hint: false,
187
+ read_only_hint: read_only_hint
188
+ }
189
+ ) do |path: nil, source_code: nil, safety: true|
190
+ result = yield(path, source_code, safety)
191
+
192
+ ::MCP::Tool::Response.new([{ type: 'text', text: result }])
193
+ rescue RuboCop::Error => e
194
+ ::MCP::Tool::Response.new([{ type: 'text', text: e.message }], error: true)
195
+ end
196
+ end
197
+ # rubocop:enable Metrics/MethodLength, Metrics/ParameterLists
198
+ end
199
+ end
200
+ end