rubocop 1.84.2 → 1.86.2

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 (210) hide show
  1. checksums.yaml +4 -4
  2. data/config/default.yml +99 -16
  3. data/config/obsoletion.yml +5 -0
  4. data/lib/rubocop/cache_config.rb +1 -1
  5. data/lib/rubocop/cli/command/auto_generate_config.rb +28 -2
  6. data/lib/rubocop/cli/command/list_enabled_cops_for.rb +40 -0
  7. data/lib/rubocop/cli/command/mcp.rb +19 -0
  8. data/lib/rubocop/cli/command/show_cops.rb +2 -2
  9. data/lib/rubocop/cli/command/show_docs_url.rb +4 -8
  10. data/lib/rubocop/cli/command/suggest_extensions.rb +1 -1
  11. data/lib/rubocop/cli.rb +7 -7
  12. data/lib/rubocop/comment_config.rb +12 -15
  13. data/lib/rubocop/config.rb +14 -10
  14. data/lib/rubocop/config_finder.rb +1 -1
  15. data/lib/rubocop/config_loader_resolver.rb +2 -1
  16. data/lib/rubocop/config_obsoletion/extracted_cop.rb +4 -2
  17. data/lib/rubocop/config_store.rb +1 -1
  18. data/lib/rubocop/config_validator.rb +1 -1
  19. data/lib/rubocop/cop/autocorrect_logic.rb +2 -1
  20. data/lib/rubocop/cop/correctors/condition_corrector.rb +1 -1
  21. data/lib/rubocop/cop/correctors/multiline_literal_brace_corrector.rb +1 -5
  22. data/lib/rubocop/cop/correctors/percent_literal_corrector.rb +2 -2
  23. data/lib/rubocop/cop/correctors.rb +28 -0
  24. data/lib/rubocop/cop/documentation.rb +2 -3
  25. data/lib/rubocop/cop/exclude_limit.rb +31 -5
  26. data/lib/rubocop/cop/gemspec/require_mfa.rb +4 -4
  27. data/lib/rubocop/cop/internal_affairs/itblock_handler.rb +69 -0
  28. data/lib/rubocop/cop/internal_affairs/location_line_equality_comparison.rb +1 -0
  29. data/lib/rubocop/cop/internal_affairs.rb +1 -0
  30. data/lib/rubocop/cop/layout/argument_alignment.rb +2 -2
  31. data/lib/rubocop/cop/layout/array_alignment.rb +1 -1
  32. data/lib/rubocop/cop/layout/dot_position.rb +1 -1
  33. data/lib/rubocop/cop/layout/empty_line_after_guard_clause.rb +9 -2
  34. data/lib/rubocop/cop/layout/empty_line_between_defs.rb +1 -1
  35. data/lib/rubocop/cop/layout/empty_lines_around_attribute_accessor.rb +1 -0
  36. data/lib/rubocop/cop/layout/empty_lines_around_block_body.rb +12 -2
  37. data/lib/rubocop/cop/layout/empty_lines_around_class_body.rb +16 -2
  38. data/lib/rubocop/cop/layout/empty_lines_around_module_body.rb +16 -2
  39. data/lib/rubocop/cop/layout/end_alignment.rb +6 -3
  40. data/lib/rubocop/cop/layout/first_hash_element_indentation.rb +7 -1
  41. data/lib/rubocop/cop/layout/hash_alignment.rb +1 -1
  42. data/lib/rubocop/cop/layout/indentation_width.rb +1 -1
  43. data/lib/rubocop/cop/layout/line_length.rb +5 -3
  44. data/lib/rubocop/cop/layout/multiline_assignment_layout.rb +9 -2
  45. data/lib/rubocop/cop/layout/multiline_method_call_brace_layout.rb +1 -1
  46. data/lib/rubocop/cop/layout/multiline_method_call_indentation.rb +53 -3
  47. data/lib/rubocop/cop/layout/parameter_alignment.rb +1 -1
  48. data/lib/rubocop/cop/layout/redundant_line_break.rb +1 -1
  49. data/lib/rubocop/cop/layout/space_around_block_parameters.rb +1 -1
  50. data/lib/rubocop/cop/layout/space_around_keyword.rb +3 -1
  51. data/lib/rubocop/cop/layout/space_in_lambda_literal.rb +1 -0
  52. data/lib/rubocop/cop/lint/constant_reassignment.rb +59 -9
  53. data/lib/rubocop/cop/lint/constant_resolution.rb +1 -1
  54. data/lib/rubocop/cop/lint/data_define_override.rb +63 -0
  55. data/lib/rubocop/cop/lint/duplicate_methods.rb +55 -8
  56. data/lib/rubocop/cop/lint/empty_block.rb +1 -1
  57. data/lib/rubocop/cop/lint/empty_conditional_body.rb +6 -1
  58. data/lib/rubocop/cop/lint/empty_in_pattern.rb +8 -1
  59. data/lib/rubocop/cop/lint/empty_when.rb +8 -1
  60. data/lib/rubocop/cop/lint/interpolation_check.rb +7 -2
  61. data/lib/rubocop/cop/lint/next_without_accumulator.rb +2 -0
  62. data/lib/rubocop/cop/lint/non_deterministic_require_order.rb +3 -1
  63. data/lib/rubocop/cop/lint/number_conversion.rb +1 -1
  64. data/lib/rubocop/cop/lint/parentheses_as_grouped_expression.rb +3 -13
  65. data/lib/rubocop/cop/lint/redundant_cop_enable_directive.rb +0 -9
  66. data/lib/rubocop/cop/lint/redundant_safe_navigation.rb +23 -6
  67. data/lib/rubocop/cop/lint/require_relative_self_path.rb +2 -0
  68. data/lib/rubocop/cop/lint/safe_navigation_chain.rb +17 -0
  69. data/lib/rubocop/cop/lint/safe_navigation_consistency.rb +7 -1
  70. data/lib/rubocop/cop/lint/syntax.rb +25 -1
  71. data/lib/rubocop/cop/lint/trailing_comma_in_attribute_declaration.rb +1 -0
  72. data/lib/rubocop/cop/lint/unmodified_reduce_accumulator.rb +1 -0
  73. data/lib/rubocop/cop/lint/unreachable_pattern_branch.rb +113 -0
  74. data/lib/rubocop/cop/lint/unused_method_argument.rb +10 -0
  75. data/lib/rubocop/cop/lint/useless_assignment.rb +4 -9
  76. data/lib/rubocop/cop/lint/useless_constant_scoping.rb +4 -4
  77. data/lib/rubocop/cop/lint/useless_default_value_argument.rb +2 -0
  78. data/lib/rubocop/cop/lint/utils/nil_receiver_checker.rb +35 -9
  79. data/lib/rubocop/cop/lint/void.rb +32 -12
  80. data/lib/rubocop/cop/metrics/block_nesting.rb +23 -0
  81. data/lib/rubocop/cop/migration/department_name.rb +12 -1
  82. data/lib/rubocop/cop/mixin/check_line_breakable.rb +1 -1
  83. data/lib/rubocop/cop/mixin/check_single_line_suitability.rb +2 -2
  84. data/lib/rubocop/cop/mixin/configurable_max.rb +6 -5
  85. data/lib/rubocop/cop/mixin/hash_transform_method/autocorrection.rb +63 -0
  86. data/lib/rubocop/cop/mixin/hash_transform_method.rb +10 -60
  87. data/lib/rubocop/cop/mixin.rb +85 -0
  88. data/lib/rubocop/cop/naming/block_parameter_name.rb +1 -1
  89. data/lib/rubocop/cop/naming/memoized_instance_variable_name.rb +1 -1
  90. data/lib/rubocop/cop/offense.rb +8 -0
  91. data/lib/rubocop/cop/registry.rb +39 -37
  92. data/lib/rubocop/cop/security/eval.rb +15 -2
  93. data/lib/rubocop/cop/style/access_modifier_declarations.rb +14 -2
  94. data/lib/rubocop/cop/style/accessor_grouping.rb +4 -2
  95. data/lib/rubocop/cop/style/alias.rb +4 -1
  96. data/lib/rubocop/cop/style/and_or.rb +1 -0
  97. data/lib/rubocop/cop/style/arguments_forwarding.rb +25 -7
  98. data/lib/rubocop/cop/style/array_join.rb +4 -2
  99. data/lib/rubocop/cop/style/ascii_comments.rb +6 -3
  100. data/lib/rubocop/cop/style/attr.rb +5 -2
  101. data/lib/rubocop/cop/style/bare_percent_literals.rb +3 -1
  102. data/lib/rubocop/cop/style/begin_block.rb +3 -1
  103. data/lib/rubocop/cop/style/block_delimiters.rb +25 -33
  104. data/lib/rubocop/cop/style/case_equality.rb +4 -0
  105. data/lib/rubocop/cop/style/class_and_module_children.rb +10 -2
  106. data/lib/rubocop/cop/style/collection_compact.rb +36 -16
  107. data/lib/rubocop/cop/style/colon_method_call.rb +3 -1
  108. data/lib/rubocop/cop/style/concat_array_literals.rb +2 -0
  109. data/lib/rubocop/cop/style/conditional_assignment.rb +0 -4
  110. data/lib/rubocop/cop/style/copyright.rb +22 -11
  111. data/lib/rubocop/cop/style/date_time.rb +2 -2
  112. data/lib/rubocop/cop/style/document_dynamic_eval_definition.rb +6 -1
  113. data/lib/rubocop/cop/style/each_for_simple_loop.rb +1 -1
  114. data/lib/rubocop/cop/style/each_with_object.rb +2 -0
  115. data/lib/rubocop/cop/style/empty_block_parameter.rb +1 -1
  116. data/lib/rubocop/cop/style/empty_class_definition.rb +43 -20
  117. data/lib/rubocop/cop/style/empty_lambda_parameter.rb +1 -1
  118. data/lib/rubocop/cop/style/encoding.rb +7 -1
  119. data/lib/rubocop/cop/style/end_block.rb +3 -1
  120. data/lib/rubocop/cop/style/endless_method.rb +8 -3
  121. data/lib/rubocop/cop/style/file_open.rb +84 -0
  122. data/lib/rubocop/cop/style/for.rb +3 -0
  123. data/lib/rubocop/cop/style/format_string_token.rb +29 -2
  124. data/lib/rubocop/cop/style/global_vars.rb +5 -2
  125. data/lib/rubocop/cop/style/guard_clause.rb +9 -6
  126. data/lib/rubocop/cop/style/hash_as_last_array_item.rb +21 -5
  127. data/lib/rubocop/cop/style/hash_lookup_method.rb +19 -7
  128. data/lib/rubocop/cop/style/hash_transform_keys.rb +17 -7
  129. data/lib/rubocop/cop/style/hash_transform_values.rb +17 -7
  130. data/lib/rubocop/cop/style/if_inside_else.rb +16 -7
  131. data/lib/rubocop/cop/style/if_unless_modifier.rb +14 -3
  132. data/lib/rubocop/cop/style/if_with_semicolon.rb +7 -5
  133. data/lib/rubocop/cop/style/inline_comment.rb +4 -1
  134. data/lib/rubocop/cop/style/ip_addresses.rb +1 -2
  135. data/lib/rubocop/cop/style/magic_comment_format.rb +2 -2
  136. data/lib/rubocop/cop/style/map_join.rb +123 -0
  137. data/lib/rubocop/cop/style/method_call_with_args_parentheses/require_parentheses.rb +5 -3
  138. data/lib/rubocop/cop/style/module_member_existence_check.rb +7 -14
  139. data/lib/rubocop/cop/style/multiline_if_then.rb +3 -1
  140. data/lib/rubocop/cop/style/mutable_constant.rb +1 -1
  141. data/lib/rubocop/cop/style/nil_comparison.rb +2 -3
  142. data/lib/rubocop/cop/style/nil_lambda.rb +1 -1
  143. data/lib/rubocop/cop/style/non_nil_check.rb +5 -11
  144. data/lib/rubocop/cop/style/not.rb +2 -0
  145. data/lib/rubocop/cop/style/numeric_literals.rb +3 -2
  146. data/lib/rubocop/cop/style/one_class_per_file.rb +115 -0
  147. data/lib/rubocop/cop/style/one_line_conditional.rb +4 -3
  148. data/lib/rubocop/cop/style/parallel_assignment.rb +4 -0
  149. data/lib/rubocop/cop/style/partition_instead_of_double_select.rb +270 -0
  150. data/lib/rubocop/cop/style/percent_literal_delimiters.rb +2 -0
  151. data/lib/rubocop/cop/style/predicate_with_kind.rb +84 -0
  152. data/lib/rubocop/cop/style/proc.rb +3 -2
  153. data/lib/rubocop/cop/style/raise_args.rb +1 -1
  154. data/lib/rubocop/cop/style/reduce_to_hash.rb +200 -0
  155. data/lib/rubocop/cop/style/redundant_begin.rb +3 -3
  156. data/lib/rubocop/cop/style/redundant_each.rb +3 -3
  157. data/lib/rubocop/cop/style/redundant_fetch_block.rb +1 -1
  158. data/lib/rubocop/cop/style/redundant_interpolation_unfreeze.rb +26 -10
  159. data/lib/rubocop/cop/style/redundant_line_continuation.rb +16 -0
  160. data/lib/rubocop/cop/style/redundant_min_max_by.rb +93 -0
  161. data/lib/rubocop/cop/style/redundant_parentheses.rb +25 -22
  162. data/lib/rubocop/cop/style/redundant_percent_q.rb +4 -1
  163. data/lib/rubocop/cop/style/redundant_return.rb +3 -1
  164. data/lib/rubocop/cop/style/redundant_self.rb +2 -2
  165. data/lib/rubocop/cop/style/redundant_self_assignment_branch.rb +0 -5
  166. data/lib/rubocop/cop/style/redundant_struct_keyword_init.rb +114 -0
  167. data/lib/rubocop/cop/style/regexp_literal.rb +29 -0
  168. data/lib/rubocop/cop/style/safe_navigation.rb +7 -7
  169. data/lib/rubocop/cop/style/select_by_kind.rb +158 -0
  170. data/lib/rubocop/cop/style/select_by_range.rb +197 -0
  171. data/lib/rubocop/cop/style/select_by_regexp.rb +51 -21
  172. data/lib/rubocop/cop/style/semicolon.rb +2 -0
  173. data/lib/rubocop/cop/style/single_line_block_params.rb +2 -2
  174. data/lib/rubocop/cop/style/single_line_do_end_block.rb +1 -1
  175. data/lib/rubocop/cop/style/single_line_methods.rb +3 -1
  176. data/lib/rubocop/cop/style/sole_nested_conditional.rb +4 -2
  177. data/lib/rubocop/cop/style/special_global_vars.rb +6 -1
  178. data/lib/rubocop/cop/style/symbol_proc.rb +7 -6
  179. data/lib/rubocop/cop/style/tally_method.rb +181 -0
  180. data/lib/rubocop/cop/style/trailing_comma_in_block_args.rb +1 -1
  181. data/lib/rubocop/cop/style/trailing_method_end_statement.rb +1 -0
  182. data/lib/rubocop/cop/style/while_until_modifier.rb +16 -0
  183. data/lib/rubocop/cop/style/yoda_expression.rb +1 -1
  184. data/lib/rubocop/cop/team.rb +86 -35
  185. data/lib/rubocop/cop/variable_force/branch.rb +2 -2
  186. data/lib/rubocop/directive_comment.rb +2 -1
  187. data/lib/rubocop/formatter/disabled_config_formatter.rb +5 -2
  188. data/lib/rubocop/formatter/formatter_set.rb +1 -1
  189. data/lib/rubocop/formatter/junit_formatter.rb +1 -1
  190. data/lib/rubocop/formatter/simple_text_formatter.rb +0 -2
  191. data/lib/rubocop/formatter/worst_offenders_formatter.rb +1 -1
  192. data/lib/rubocop/formatter.rb +22 -21
  193. data/lib/rubocop/lsp/diagnostic.rb +1 -0
  194. data/lib/rubocop/lsp/routes.rb +10 -3
  195. data/lib/rubocop/lsp/runtime.rb +1 -2
  196. data/lib/rubocop/mcp/server.rb +200 -0
  197. data/lib/rubocop/options.rb +17 -4
  198. data/lib/rubocop/path_util.rb +14 -2
  199. data/lib/rubocop/plugin/loader.rb +1 -1
  200. data/lib/rubocop/result_cache.rb +22 -10
  201. data/lib/rubocop/rspec/cop_helper.rb +8 -0
  202. data/lib/rubocop/rspec/shared_contexts.rb +32 -2
  203. data/lib/rubocop/runner.rb +78 -51
  204. data/lib/rubocop/server/cache.rb +5 -7
  205. data/lib/rubocop/server/core.rb +2 -0
  206. data/lib/rubocop/target_finder.rb +14 -7
  207. data/lib/rubocop/target_ruby.rb +18 -12
  208. data/lib/rubocop/version.rb +2 -2
  209. data/lib/rubocop.rb +21 -96
  210. metadata +25 -5
@@ -16,6 +16,11 @@ module RuboCop
16
16
  # # good
17
17
  # x += 1 while x < 10
18
18
  #
19
+ # # good
20
+ # while x < 10
21
+ # y += 1 if x.odd?
22
+ # end
23
+ #
19
24
  # # bad
20
25
  # until x > 10
21
26
  # x += 1
@@ -24,6 +29,11 @@ module RuboCop
24
29
  # # good
25
30
  # x += 1 until x > 10
26
31
  #
32
+ # # good
33
+ # until x > 10
34
+ # y += 1 unless x.even?
35
+ # end
36
+ #
27
37
  # # bad
28
38
  # x += 100 while x < 500 # a long comment that makes code too long if it were a single line
29
39
  #
@@ -45,6 +55,12 @@ module RuboCop
45
55
  end
46
56
  end
47
57
  alias on_until on_while
58
+
59
+ private
60
+
61
+ def non_eligible_body?(body)
62
+ body&.conditional? || super
63
+ end
48
64
  end
49
65
  end
50
66
  end
@@ -76,7 +76,7 @@ module RuboCop
76
76
  end
77
77
 
78
78
  def supported_operators
79
- Array(cop_config['SupportedOperators'])
79
+ @supported_operators ||= Array(cop_config['SupportedOperators']).freeze
80
80
  end
81
81
 
82
82
  def offended_ancestor?(node)
@@ -11,6 +11,9 @@ module RuboCop
11
11
  # (unless autocorrections happened).
12
12
  # rubocop:disable Metrics/ClassLength
13
13
  class Team
14
+ InvestigationResult = Struct.new(:report, :corrector)
15
+ private_constant :InvestigationResult
16
+
14
17
  # @return [Team]
15
18
  def self.new(cop_or_classes, config, options = {})
16
19
  # Support v0 api:
@@ -89,31 +92,25 @@ module RuboCop
89
92
 
90
93
  # @return [Commissioner::InvestigationReport]
91
94
  def investigate(processed_source, offset: 0, original: processed_source)
92
- be_ready
93
-
94
- # The autocorrection process may have to be repeated multiple times
95
- # until there are no corrections left to perform
96
- # To speed things up, run autocorrecting cops by themselves, and only
97
- # run the other cops when no corrections are left
98
- on_duty = roundup_relevant_cops(processed_source)
95
+ result = investigate_with_corrector(processed_source, offset: offset, original: original)
96
+ autocorrect(processed_source, result.corrector)
97
+ result.report
98
+ end
99
99
 
100
- autocorrect_cops, other_cops = on_duty.partition(&:autocorrect?)
101
- report = investigate_partial(autocorrect_cops, processed_source,
102
- offset: offset, original: original)
100
+ # @return [Array<Offense>]
101
+ def investigate_fragments(fragments, original:)
102
+ @updated_source_file = false
103
103
 
104
- unless autocorrect(processed_source, report, offset: offset, original: original)
105
- # If we corrected some errors, another round of inspection will be
106
- # done, and any other offenses will be caught then, so only need
107
- # to check other_cops if no correction was done
108
- report = report.merge(investigate_partial(other_cops, processed_source,
109
- offset: offset, original: original))
110
- end
104
+ offenses, errors, warnings, corrector =
105
+ fragments.each_with_object([[], [], [], nil]) do |fragment, data|
106
+ investigate_fragment(fragment, original, data)
107
+ end
111
108
 
112
- process_errors(processed_source.path, report.errors)
109
+ autocorrect(original, corrector)
110
+ @errors = errors
111
+ @warnings = warnings
113
112
 
114
- report
115
- ensure
116
- @ready = false
113
+ offenses
117
114
  end
118
115
 
119
116
  # @deprecated
@@ -136,14 +133,13 @@ module RuboCop
136
133
 
137
134
  private
138
135
 
139
- def autocorrect(processed_source, report, original:, offset:)
136
+ def autocorrect(processed_source, corrector)
140
137
  @updated_source_file = false
141
138
  return unless autocorrect?
142
- return if report.processed_source.parser_error
139
+ return unless corrector
140
+ return if corrector.empty?
143
141
 
144
- new_source = autocorrect_report(report, original: original, offset: offset)
145
-
146
- return unless new_source
142
+ new_source = corrector.rewrite
147
143
 
148
144
  if @options[:stdin]
149
145
  # holds source read in from stdin, when --stdin option is used
@@ -174,6 +170,54 @@ module RuboCop
174
170
  commissioner.investigate(processed_source, offset: offset, original: original)
175
171
  end
176
172
 
173
+ def investigate_with_corrector(processed_source, offset:, original:)
174
+ be_ready
175
+
176
+ # The autocorrection process may have to be repeated multiple times
177
+ # until there are no corrections left to perform
178
+ # To speed things up, run autocorrecting cops by themselves, and only
179
+ # run the other cops when no corrections are left
180
+ on_duty = roundup_relevant_cops(processed_source)
181
+
182
+ autocorrect_cops, other_cops = on_duty.partition(&:autocorrect?)
183
+ report = investigate_partial(autocorrect_cops, processed_source,
184
+ offset: offset, original: original)
185
+
186
+ corrector = collated_corrector(report, offset: offset, original: original)
187
+
188
+ unless corrector
189
+ # If we corrected some errors, another round of inspection will be
190
+ # done, and any other offenses will be caught then, so only need
191
+ # to check other_cops if no correction was done
192
+ report = report.merge(investigate_partial(other_cops, processed_source,
193
+ offset: offset, original: original))
194
+ end
195
+
196
+ process_errors(processed_source.path, report.errors)
197
+
198
+ InvestigationResult.new(report, corrector)
199
+ ensure
200
+ @ready = false
201
+ end
202
+
203
+ def investigate_fragment(fragment, original, data)
204
+ offenses, errors, warnings, corrector = data
205
+ result = investigate_with_corrector(
206
+ fragment[:processed_source],
207
+ offset: fragment[:offset],
208
+ original: original
209
+ )
210
+
211
+ offenses.concat(result.report.offenses)
212
+ if result.corrector
213
+ corrector ||= Corrector.new(original)
214
+ merge_corrector!(corrector, result.corrector, offset: 0)
215
+ data[3] = corrector
216
+ end
217
+ errors.concat(@errors)
218
+ warnings.concat(@warnings)
219
+ end
220
+
177
221
  # @return [Array<cop>]
178
222
  def roundup_relevant_cops(processed_source)
179
223
  cops.select do |cop|
@@ -200,28 +244,35 @@ module RuboCop
200
244
  cop.class.support_target_rails_version?(cop.target_rails_version)
201
245
  end
202
246
 
203
- def autocorrect_report(report, offset:, original:)
247
+ def collated_corrector(report, offset:, original:)
248
+ return unless autocorrect?
249
+ return if report.processed_source.parser_error
250
+
204
251
  corrector = collate_corrections(report, offset: offset, original: original)
205
252
 
206
- corrector.rewrite unless corrector.empty?
253
+ corrector unless corrector.empty?
207
254
  end
208
255
 
209
256
  def collate_corrections(report, offset:, original:)
210
257
  corrector = Corrector.new(original)
211
258
 
212
259
  each_corrector(report) do |to_merge|
213
- suppress_clobbering do
214
- if corrector.source_buffer == to_merge.source_buffer
215
- corrector.merge!(to_merge)
216
- else
217
- corrector.import!(to_merge, offset: offset)
218
- end
219
- end
260
+ merge_corrector!(corrector, to_merge, offset: offset)
220
261
  end
221
262
 
222
263
  corrector
223
264
  end
224
265
 
266
+ def merge_corrector!(corrector, to_merge, offset:)
267
+ suppress_clobbering do
268
+ if corrector.source_buffer == to_merge.source_buffer
269
+ corrector.merge!(to_merge)
270
+ else
271
+ corrector.import!(to_merge, offset: offset)
272
+ end
273
+ end
274
+ end
275
+
225
276
  def each_corrector(report)
226
277
  skips = Set.new
227
278
  report.cop_reports.each do |cop_report|
@@ -346,8 +346,8 @@ module RuboCop
346
346
  end
347
347
  end
348
348
 
349
- CLASSES_BY_TYPE = Base.classes.each_with_object({}) do |klass, classes|
350
- classes[klass.type] = klass
349
+ CLASSES_BY_TYPE = Base.classes.to_h do |klass|
350
+ [klass.type, klass]
351
351
  end
352
352
  end
353
353
  end
@@ -52,7 +52,8 @@ module RuboCop
52
52
  def initialize(comment, cop_registry = Cop::Registry.global)
53
53
  @comment = comment
54
54
  @cop_registry = cop_registry
55
- @match_data = comment.text.match(DIRECTIVE_COMMENT_REGEXP)
55
+ match_data = comment.text.match(DIRECTIVE_COMMENT_REGEXP)
56
+ @match_data = match_data&.pre_match&.match?(/\A#\s*\z/) ? nil : match_data
56
57
  @mode, @cops = match_captures
57
58
  end
58
59
 
@@ -131,6 +131,9 @@ module RuboCop
131
131
  end
132
132
 
133
133
  def set_max(cfg, cop_name)
134
+ exclude_limits = RuboCop::ExcludeLimit.read_limits(cop_name)
135
+ cfg[:exclude_limit] = exclude_limits unless exclude_limits.empty?
136
+
134
137
  return unless cfg[:exclude_limit]
135
138
 
136
139
  cfg.merge!(cfg[:exclude_limit]) if should_set_max?(cop_name)
@@ -192,7 +195,7 @@ module RuboCop
192
195
  next unless value.is_a?(Array)
193
196
  next if value.empty?
194
197
 
195
- value.map! { |v| v.nil? ? '~' : v } # Change nil back to ~ as in the YAML file.
198
+ value = value.map { |v| v.nil? ? '~' : v } # Change nil back to ~ as in the YAML file.
196
199
  output_buffer.puts "# #{param}: #{value.uniq.join(', ')}"
197
200
  end
198
201
  end
@@ -233,7 +236,7 @@ module RuboCop
233
236
 
234
237
  def output_exclude_list(output_buffer, offending_files, cop_name)
235
238
  require 'pathname'
236
- parent = Pathname.new(Dir.pwd)
239
+ parent = Pathname.new(PathUtil.pwd)
237
240
 
238
241
  output_buffer.puts ' Exclude:'
239
242
  excludes(offending_files, cop_name, parent).each do |exclude_path|
@@ -57,7 +57,7 @@ module RuboCop
57
57
  if output_path
58
58
  dir_path = File.dirname(output_path)
59
59
  FileUtils.mkdir_p(dir_path)
60
- output = File.open(output_path, 'w')
60
+ output = File.open(output_path, 'w') # rubocop:disable Style/FileOpen
61
61
  else
62
62
  output = $stdout
63
63
  end
@@ -93,7 +93,7 @@ module RuboCop
93
93
 
94
94
  def classname_attribute_value(file)
95
95
  @classname_attribute_value_cache ||= Hash.new do |hash, key|
96
- hash[key] = key.delete_suffix('.rb').gsub("#{Dir.pwd}/", '').tr('/', '.')
96
+ hash[key] = key.delete_suffix('.rb').gsub("#{PathUtil.pwd}/", '').tr('/', '.')
97
97
  end
98
98
  @classname_attribute_value_cache[file]
99
99
  end
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'colorizable'
4
-
5
3
  module RuboCop
6
4
  module Formatter
7
5
  # A basic formatter that displays only files with offenses.
@@ -24,7 +24,7 @@ module RuboCop
24
24
  def file_finished(file, offenses)
25
25
  return if offenses.empty?
26
26
 
27
- path = Pathname.new(file).relative_path_from(Pathname.new(Dir.pwd))
27
+ path = Pathname.new(file).relative_path_from(Pathname.new(PathUtil.pwd))
28
28
  @offense_counts[path] = offenses.size
29
29
  end
30
30
 
@@ -3,32 +3,33 @@
3
3
  module RuboCop
4
4
  # The bootstrap module for formatter.
5
5
  module Formatter
6
- require_relative 'formatter/text_util'
6
+ autoload :Colorizable, 'rubocop/formatter/colorizable'
7
+ autoload :TextUtil, 'rubocop/formatter/text_util'
7
8
 
8
- require_relative 'formatter/base_formatter'
9
- require_relative 'formatter/simple_text_formatter'
9
+ autoload :BaseFormatter, 'rubocop/formatter/base_formatter'
10
+ autoload :SimpleTextFormatter, 'rubocop/formatter/simple_text_formatter'
10
11
 
11
12
  # relies on simple text
12
- require_relative 'formatter/clang_style_formatter'
13
- require_relative 'formatter/disabled_config_formatter'
14
- require_relative 'formatter/emacs_style_formatter'
15
- require_relative 'formatter/file_list_formatter'
16
- require_relative 'formatter/fuubar_style_formatter'
17
- require_relative 'formatter/github_actions_formatter'
18
- require_relative 'formatter/html_formatter'
19
- require_relative 'formatter/json_formatter'
20
- require_relative 'formatter/junit_formatter'
21
- require_relative 'formatter/markdown_formatter'
22
- require_relative 'formatter/offense_count_formatter'
23
- require_relative 'formatter/pacman_formatter'
24
- require_relative 'formatter/progress_formatter'
25
- require_relative 'formatter/quiet_formatter'
26
- require_relative 'formatter/tap_formatter'
27
- require_relative 'formatter/worst_offenders_formatter'
13
+ autoload :ClangStyleFormatter, 'rubocop/formatter/clang_style_formatter'
14
+ autoload :DisabledConfigFormatter, 'rubocop/formatter/disabled_config_formatter'
15
+ autoload :EmacsStyleFormatter, 'rubocop/formatter/emacs_style_formatter'
16
+ autoload :FileListFormatter, 'rubocop/formatter/file_list_formatter'
17
+ autoload :FuubarStyleFormatter, 'rubocop/formatter/fuubar_style_formatter'
18
+ autoload :GitHubActionsFormatter, 'rubocop/formatter/github_actions_formatter'
19
+ autoload :HTMLFormatter, 'rubocop/formatter/html_formatter'
20
+ autoload :JSONFormatter, 'rubocop/formatter/json_formatter'
21
+ autoload :JUnitFormatter, 'rubocop/formatter/junit_formatter'
22
+ autoload :MarkdownFormatter, 'rubocop/formatter/markdown_formatter'
23
+ autoload :OffenseCountFormatter, 'rubocop/formatter/offense_count_formatter'
24
+ autoload :PacmanFormatter, 'rubocop/formatter/pacman_formatter'
25
+ autoload :ProgressFormatter, 'rubocop/formatter/progress_formatter'
26
+ autoload :QuietFormatter, 'rubocop/formatter/quiet_formatter'
27
+ autoload :TapFormatter, 'rubocop/formatter/tap_formatter'
28
+ autoload :WorstOffendersFormatter, 'rubocop/formatter/worst_offenders_formatter'
28
29
 
29
30
  # relies on progress formatter
30
- require_relative 'formatter/auto_gen_config_formatter'
31
+ autoload :AutoGenConfigFormatter, 'rubocop/formatter/auto_gen_config_formatter'
31
32
 
32
- require_relative 'formatter/formatter_set'
33
+ autoload :FormatterSet, 'rubocop/formatter/formatter_set'
33
34
  end
34
35
  end
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'language_server-protocol'
3
4
  require_relative 'disable_comment_edits'
4
5
  require_relative 'severity'
5
6
 
@@ -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|
@@ -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
@@ -63,7 +62,7 @@ module RuboCop
63
62
  document_encoding,
64
63
  offense,
65
64
  path,
66
- @cop_registry[offense.cop_name]&.first,
65
+ RuboCop::Cop::Registry.global.find_by_cop_name(offense.cop_name),
67
66
  processed_source
68
67
  ).to_lsp_diagnostic(config)
69
68
  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