rubocop 1.91.0 → 1.92.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 (236) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  3. data/config/default.yml +3 -1
  4. data/lib/rubocop/cached_data.rb +47 -7
  5. data/lib/rubocop/changed_files.rb +35 -13
  6. data/lib/rubocop/cli/command/cop_names.rb +57 -0
  7. data/lib/rubocop/cli/command/execute_runner.rb +2 -2
  8. data/lib/rubocop/cli/command/explain.rb +34 -0
  9. data/lib/rubocop/cli/command/show_cops.rb +6 -0
  10. data/lib/rubocop/cli/command/show_docs_url.rb +6 -4
  11. data/lib/rubocop/cli.rb +3 -2
  12. data/lib/rubocop/comment_config.rb +17 -8
  13. data/lib/rubocop/config_loader.rb +6 -3
  14. data/lib/rubocop/config_loader_resolver.rb +37 -12
  15. data/lib/rubocop/config_obsoletion/removed_cop.rb +1 -1
  16. data/lib/rubocop/config_validator.rb +11 -0
  17. data/lib/rubocop/cop/autocorrect_logic.rb +10 -5
  18. data/lib/rubocop/cop/base.rb +42 -21
  19. data/lib/rubocop/cop/bundler/gem_comment.rb +0 -1
  20. data/lib/rubocop/cop/bundler/ordered_gems.rb +0 -8
  21. data/lib/rubocop/cop/cop_documentation.rb +81 -0
  22. data/lib/rubocop/cop/cop_explanation.rb +140 -0
  23. data/lib/rubocop/cop/correctors/alignment_corrector.rb +13 -1
  24. data/lib/rubocop/cop/correctors/each_to_for_corrector.rb +22 -7
  25. data/lib/rubocop/cop/correctors/empty_line_corrector.rb +1 -1
  26. data/lib/rubocop/cop/correctors/lambda_literal_to_method_corrector.rb +23 -2
  27. data/lib/rubocop/cop/correctors/parentheses_corrector.rb +45 -8
  28. data/lib/rubocop/cop/documentation.rb +1 -1
  29. data/lib/rubocop/cop/gemspec/dependency_version.rb +0 -6
  30. data/lib/rubocop/cop/gemspec/ordered_dependencies.rb +0 -6
  31. data/lib/rubocop/cop/internal_affairs/node_matcher_directive.rb +2 -5
  32. data/lib/rubocop/cop/internal_affairs/node_type_group.rb +0 -7
  33. data/lib/rubocop/cop/internal_affairs/redundant_context_config_parameter.rb +1 -7
  34. data/lib/rubocop/cop/internal_affairs/style_detected_api_use.rb +1 -7
  35. data/lib/rubocop/cop/layout/block_alignment.rb +27 -0
  36. data/lib/rubocop/cop/layout/case_indentation.rb +25 -9
  37. data/lib/rubocop/cop/layout/class_structure.rb +9 -1
  38. data/lib/rubocop/cop/layout/def_end_alignment.rb +4 -0
  39. data/lib/rubocop/cop/layout/empty_line_after_multiline_condition.rb +14 -5
  40. data/lib/rubocop/cop/layout/empty_lines.rb +5 -2
  41. data/lib/rubocop/cop/layout/empty_lines_around_attribute_accessor.rb +1 -0
  42. data/lib/rubocop/cop/layout/empty_lines_around_block_body.rb +1 -1
  43. data/lib/rubocop/cop/layout/empty_lines_around_exception_handling_keywords.rb +23 -1
  44. data/lib/rubocop/cop/layout/end_alignment.rb +16 -10
  45. data/lib/rubocop/cop/layout/extra_spacing.rb +4 -0
  46. data/lib/rubocop/cop/layout/first_argument_indentation.rb +1 -0
  47. data/lib/rubocop/cop/layout/first_hash_element_indentation.rb +5 -3
  48. data/lib/rubocop/cop/layout/first_method_argument_line_break.rb +6 -0
  49. data/lib/rubocop/cop/layout/first_method_parameter_line_break.rb +4 -0
  50. data/lib/rubocop/cop/layout/hash_alignment.rb +16 -12
  51. data/lib/rubocop/cop/layout/heredoc_argument_closing_parenthesis.rb +2 -0
  52. data/lib/rubocop/cop/layout/heredoc_indentation.rb +15 -0
  53. data/lib/rubocop/cop/layout/indentation_style.rb +4 -1
  54. data/lib/rubocop/cop/layout/indentation_width.rb +2 -2
  55. data/lib/rubocop/cop/layout/line_continuation_spacing.rb +8 -1
  56. data/lib/rubocop/cop/layout/line_end_string_concatenation_indentation.rb +25 -9
  57. data/lib/rubocop/cop/layout/line_length.rb +52 -18
  58. data/lib/rubocop/cop/layout/multiline_array_brace_layout.rb +12 -0
  59. data/lib/rubocop/cop/layout/multiline_block_layout.rb +8 -1
  60. data/lib/rubocop/cop/layout/multiline_hash_brace_layout.rb +7 -0
  61. data/lib/rubocop/cop/layout/multiline_method_call_brace_layout.rb +6 -0
  62. data/lib/rubocop/cop/layout/multiline_method_call_indentation.rb +14 -0
  63. data/lib/rubocop/cop/layout/multiline_method_definition_brace_layout.rb +4 -0
  64. data/lib/rubocop/cop/layout/redundant_line_break.rb +22 -1
  65. data/lib/rubocop/cop/layout/rescue_ensure_alignment.rb +15 -2
  66. data/lib/rubocop/cop/layout/single_line_block_chain.rb +10 -2
  67. data/lib/rubocop/cop/layout/space_after_not.rb +5 -0
  68. data/lib/rubocop/cop/layout/space_around_block_parameters.rb +6 -11
  69. data/lib/rubocop/cop/layout/space_around_keyword.rb +10 -0
  70. data/lib/rubocop/cop/layout/space_before_block_braces.rb +4 -1
  71. data/lib/rubocop/cop/layout/space_inside_array_literal_brackets.rb +15 -2
  72. data/lib/rubocop/cop/layout/space_inside_block_braces.rb +31 -8
  73. data/lib/rubocop/cop/lint/ambiguous_operator.rb +1 -1
  74. data/lib/rubocop/cop/lint/ambiguous_operator_precedence.rb +2 -1
  75. data/lib/rubocop/cop/lint/empty_conditional_body.rb +3 -1
  76. data/lib/rubocop/cop/lint/literal_as_condition.rb +15 -0
  77. data/lib/rubocop/cop/lint/literal_in_interpolation.rb +63 -19
  78. data/lib/rubocop/cop/lint/loop.rb +24 -2
  79. data/lib/rubocop/cop/lint/misplaced_magic_comment.rb +71 -11
  80. data/lib/rubocop/cop/lint/missing_cop_enable_directive.rb +18 -3
  81. data/lib/rubocop/cop/lint/number_conversion.rb +1 -1
  82. data/lib/rubocop/cop/lint/redundant_cop_disable_directive.rb +12 -1
  83. data/lib/rubocop/cop/lint/redundant_dir_glob_sort.rb +13 -2
  84. data/lib/rubocop/cop/lint/unescaped_bracket_in_regexp.rb +18 -1
  85. data/lib/rubocop/cop/lint/useless_assignment.rb +14 -7
  86. data/lib/rubocop/cop/lint/void.rb +8 -1
  87. data/lib/rubocop/cop/metrics/class_length.rb +4 -2
  88. data/lib/rubocop/cop/metrics/cyclomatic_complexity.rb +0 -4
  89. data/lib/rubocop/cop/metrics/module_length.rb +4 -2
  90. data/lib/rubocop/cop/mixin/alignment.rb +4 -0
  91. data/lib/rubocop/cop/mixin/check_line_breakable.rb +17 -14
  92. data/lib/rubocop/cop/mixin/check_single_line_suitability.rb +5 -1
  93. data/lib/rubocop/cop/mixin/code_length.rb +10 -2
  94. data/lib/rubocop/cop/mixin/empty_lines_around_body.rb +12 -1
  95. data/lib/rubocop/cop/mixin/endless_method_body_help.rb +69 -0
  96. data/lib/rubocop/cop/mixin/forbidden_identifiers.rb +2 -2
  97. data/lib/rubocop/cop/mixin/hash_alignment_styles.rb +15 -1
  98. data/lib/rubocop/cop/mixin/hash_shorthand_syntax.rb +26 -2
  99. data/lib/rubocop/cop/mixin/min_branches_count.rb +3 -1
  100. data/lib/rubocop/cop/mixin/multiline_element_line_breaks.rb +11 -1
  101. data/lib/rubocop/cop/mixin/multiline_literal_brace_layout.rb +9 -0
  102. data/lib/rubocop/cop/mixin/ordered_gem_node.rb +1 -1
  103. data/lib/rubocop/cop/mixin/pattern_matching_help.rb +35 -0
  104. data/lib/rubocop/cop/mixin/preceding_following_alignment.rb +17 -2
  105. data/lib/rubocop/cop/mixin/project_index_help.rb +15 -0
  106. data/lib/rubocop/cop/mixin/reparsed_equivalence.rb +13 -3
  107. data/lib/rubocop/cop/mixin/statement_modifier.rb +22 -2
  108. data/lib/rubocop/cop/mixin.rb +2 -0
  109. data/lib/rubocop/cop/naming/accessor_method_name.rb +0 -16
  110. data/lib/rubocop/cop/naming/block_forwarding.rb +1 -1
  111. data/lib/rubocop/cop/naming/heredoc_delimiter_case.rb +9 -2
  112. data/lib/rubocop/cop/naming/heredoc_delimiter_naming.rb +5 -3
  113. data/lib/rubocop/cop/naming/predicate_prefix.rb +7 -22
  114. data/lib/rubocop/cop/offense.rb +59 -3
  115. data/lib/rubocop/cop/registry.rb +1 -1
  116. data/lib/rubocop/cop/security/json_load.rb +16 -4
  117. data/lib/rubocop/cop/style/access_modifier_declarations.rb +7 -0
  118. data/lib/rubocop/cop/style/alias.rb +18 -5
  119. data/lib/rubocop/cop/style/ambiguous_endless_method_definition.rb +4 -3
  120. data/lib/rubocop/cop/style/and_or.rb +4 -0
  121. data/lib/rubocop/cop/style/arguments_forwarding.rb +4 -1
  122. data/lib/rubocop/cop/style/attr.rb +3 -4
  123. data/lib/rubocop/cop/style/bitwise_predicate.rb +6 -4
  124. data/lib/rubocop/cop/style/block_delimiters.rb +29 -3
  125. data/lib/rubocop/cop/style/case_like_if.rb +6 -3
  126. data/lib/rubocop/cop/style/class_and_module_children.rb +54 -16
  127. data/lib/rubocop/cop/style/class_methods_definitions.rb +8 -0
  128. data/lib/rubocop/cop/style/combinable_loops.rb +85 -11
  129. data/lib/rubocop/cop/style/concat_array_literals.rb +46 -7
  130. data/lib/rubocop/cop/style/conditional_assignment.rb +4 -0
  131. data/lib/rubocop/cop/style/disable_cops_within_source_code_directive.rb +65 -9
  132. data/lib/rubocop/cop/style/double_negation.rb +11 -0
  133. data/lib/rubocop/cop/style/empty_literal.rb +1 -1
  134. data/lib/rubocop/cop/style/empty_method.rb +3 -1
  135. data/lib/rubocop/cop/style/endless_method.rb +67 -6
  136. data/lib/rubocop/cop/style/exact_regexp_match.rb +45 -8
  137. data/lib/rubocop/cop/style/explicit_block_argument.rb +50 -10
  138. data/lib/rubocop/cop/style/float_division.rb +22 -2
  139. data/lib/rubocop/cop/style/for.rb +40 -2
  140. data/lib/rubocop/cop/style/format_string.rb +28 -6
  141. data/lib/rubocop/cop/style/frozen_string_literal_comment.rb +2 -1
  142. data/lib/rubocop/cop/style/guard_clause.rb +9 -1
  143. data/lib/rubocop/cop/style/hash_conversion.rb +28 -8
  144. data/lib/rubocop/cop/style/hash_lookup_method.rb +32 -9
  145. data/lib/rubocop/cop/style/hash_syntax.rb +16 -3
  146. data/lib/rubocop/cop/style/identical_conditional_branches.rb +22 -3
  147. data/lib/rubocop/cop/style/if_inside_else.rb +21 -1
  148. data/lib/rubocop/cop/style/if_unless_modifier.rb +19 -20
  149. data/lib/rubocop/cop/style/if_unless_modifier_of_if_unless.rb +4 -0
  150. data/lib/rubocop/cop/style/if_with_semicolon.rb +47 -4
  151. data/lib/rubocop/cop/style/inverse_methods.rb +24 -2
  152. data/lib/rubocop/cop/style/it_block_parameter.rb +32 -3
  153. data/lib/rubocop/cop/style/keyword_parameters_order.rb +3 -1
  154. data/lib/rubocop/cop/style/lambda.rb +18 -0
  155. data/lib/rubocop/cop/style/lambda_call.rb +7 -2
  156. data/lib/rubocop/cop/style/map_into_array.rb +1 -2
  157. data/lib/rubocop/cop/style/method_call_with_args_parentheses/omit_parentheses.rb +45 -10
  158. data/lib/rubocop/cop/style/method_call_with_args_parentheses.rb +25 -2
  159. data/lib/rubocop/cop/style/method_call_without_args_parentheses.rb +26 -3
  160. data/lib/rubocop/cop/style/method_def_parentheses.rb +29 -3
  161. data/lib/rubocop/cop/style/missing_else.rb +7 -1
  162. data/lib/rubocop/cop/style/multiline_if_modifier.rb +45 -5
  163. data/lib/rubocop/cop/style/multiline_in_pattern_then.rb +7 -0
  164. data/lib/rubocop/cop/style/multiline_method_signature.rb +2 -5
  165. data/lib/rubocop/cop/style/multiline_ternary_operator.rb +7 -3
  166. data/lib/rubocop/cop/style/multiline_when_then.rb +7 -0
  167. data/lib/rubocop/cop/style/mutable_constant.rb +28 -1
  168. data/lib/rubocop/cop/style/negated_if_else_condition.rb +2 -1
  169. data/lib/rubocop/cop/style/negative_array_index.rb +9 -1
  170. data/lib/rubocop/cop/style/nested_modifier.rb +27 -8
  171. data/lib/rubocop/cop/style/nested_ternary_operator.rb +26 -8
  172. data/lib/rubocop/cop/style/next.rb +24 -10
  173. data/lib/rubocop/cop/style/nil_comparison.rb +16 -4
  174. data/lib/rubocop/cop/style/non_nil_check.rb +1 -1
  175. data/lib/rubocop/cop/style/not.rb +10 -5
  176. data/lib/rubocop/cop/style/numeric_predicate.rb +21 -5
  177. data/lib/rubocop/cop/style/one_line_conditional.rb +13 -2
  178. data/lib/rubocop/cop/style/percent_literal_delimiters.rb +14 -3
  179. data/lib/rubocop/cop/style/reduce_to_hash.rb +17 -1
  180. data/lib/rubocop/cop/style/redundant_begin.rb +20 -3
  181. data/lib/rubocop/cop/style/redundant_condition.rb +30 -13
  182. data/lib/rubocop/cop/style/redundant_double_splat_hash_braces.rb +6 -0
  183. data/lib/rubocop/cop/style/redundant_exception.rb +15 -3
  184. data/lib/rubocop/cop/style/redundant_fetch_block.rb +9 -1
  185. data/lib/rubocop/cop/style/redundant_freeze.rb +10 -1
  186. data/lib/rubocop/cop/style/redundant_parentheses.rb +12 -1
  187. data/lib/rubocop/cop/style/redundant_regexp_argument.rb +7 -0
  188. data/lib/rubocop/cop/style/redundant_regexp_constructor.rb +20 -0
  189. data/lib/rubocop/cop/style/redundant_regexp_escape.rb +4 -0
  190. data/lib/rubocop/cop/style/regexp_literal.rb +75 -26
  191. data/lib/rubocop/cop/style/require_order.rb +9 -0
  192. data/lib/rubocop/cop/style/rescue_modifier.rb +5 -9
  193. data/lib/rubocop/cop/style/return_nil.rb +17 -2
  194. data/lib/rubocop/cop/style/return_nil_in_predicate_method_definition.rb +11 -1
  195. data/lib/rubocop/cop/style/sample.rb +9 -8
  196. data/lib/rubocop/cop/style/self_assignment.rb +12 -15
  197. data/lib/rubocop/cop/style/semicolon.rb +16 -5
  198. data/lib/rubocop/cop/style/single_line_methods.rb +16 -4
  199. data/lib/rubocop/cop/style/stabby_lambda_parentheses.rb +29 -5
  200. data/lib/rubocop/cop/style/string_concatenation.rb +9 -3
  201. data/lib/rubocop/cop/style/super_arguments.rb +3 -0
  202. data/lib/rubocop/cop/style/symbol_proc.rb +23 -5
  203. data/lib/rubocop/cop/style/ternary_parentheses.rb +23 -1
  204. data/lib/rubocop/cop/style/trailing_underscore_variable.rb +23 -19
  205. data/lib/rubocop/cop/style/while_until_modifier.rb +1 -1
  206. data/lib/rubocop/cop/style/yoda_condition.rb +24 -9
  207. data/lib/rubocop/cop/style/yoda_expression.rb +10 -1
  208. data/lib/rubocop/cop/team.rb +10 -5
  209. data/lib/rubocop/cop/variable_force/assignment.rb +1 -1
  210. data/lib/rubocop/cop/variable_force/variable.rb +14 -1
  211. data/lib/rubocop/formatter/base_formatter.rb +22 -0
  212. data/lib/rubocop/formatter/formatter_set.rb +4 -0
  213. data/lib/rubocop/formatter/json_formatter.rb +71 -1
  214. data/lib/rubocop/formatter/offense_count_formatter.rb +3 -1
  215. data/lib/rubocop/formatter/pacman_formatter.rb +3 -1
  216. data/lib/rubocop/formatter/sarif_formatter.rb +12 -7
  217. data/lib/rubocop/formatter/worst_offenders_formatter.rb +3 -1
  218. data/lib/rubocop/lsp/runtime.rb +47 -11
  219. data/lib/rubocop/lsp/stdin_runner.rb +11 -5
  220. data/lib/rubocop/lsp.rb +5 -2
  221. data/lib/rubocop/mcp/autocorrection_request.rb +52 -0
  222. data/lib/rubocop/mcp/explain_tool.rb +68 -0
  223. data/lib/rubocop/mcp/scope.rb +65 -0
  224. data/lib/rubocop/mcp/server.rb +184 -76
  225. data/lib/rubocop/mcp/source_files.rb +35 -0
  226. data/lib/rubocop/offense_limit.rb +30 -0
  227. data/lib/rubocop/options.rb +45 -10
  228. data/lib/rubocop/plugin/configuration_integrator.rb +12 -5
  229. data/lib/rubocop/remote_config.rb +4 -1
  230. data/lib/rubocop/result_cache.rb +20 -13
  231. data/lib/rubocop/runner.rb +109 -41
  232. data/lib/rubocop/server/cache.rb +2 -2
  233. data/lib/rubocop/util.rb +69 -0
  234. data/lib/rubocop/version.rb +1 -1
  235. data/lib/rubocop.rb +5 -0
  236. metadata +16 -5
@@ -53,7 +53,7 @@ module RuboCop
53
53
  Lint/RedundantCopDisableDirective RedundantCopDisableDirective Lint
54
54
  ].freeze
55
55
 
56
- attr_reader :errors, :warnings, :diffs
56
+ attr_reader :errors, :warnings
57
57
  attr_writer :aborting
58
58
 
59
59
  def initialize(options, config_store)
@@ -63,10 +63,20 @@ module RuboCop
63
63
  @warnings = []
64
64
  @aborting = false
65
65
  @inspected_files = []
66
- @diffs = []
66
+ @diff_sources = {}
67
67
  @report_queue = {}
68
68
  end
69
69
 
70
+ # The `--diff` patches, one per file, from the source on disk (or stdin) to
71
+ # the source once every correction, redundant directive removal included,
72
+ # has been applied.
73
+ def diffs
74
+ @diffs ||= @diff_sources.filter_map do |file, (original_source, corrected_source)|
75
+ diff = UnifiedDiff.new(PathUtil.smart_path(file), original_source, corrected_source).to_s
76
+ diff unless diff.empty?
77
+ end
78
+ end
79
+
70
80
  def run(paths)
71
81
  @inspection_team_by_config = {}.compare_by_identity
72
82
 
@@ -101,9 +111,7 @@ module RuboCop
101
111
  def only_changed(target_files)
102
112
  return target_files unless @options[:changed]
103
113
 
104
- changed = ChangedFiles.new(@options[:changed]).paths
105
-
106
- target_files.select { |file| changed.include?(file) }.freeze
114
+ ChangedFiles.new(@options[:changed]).filter(target_files).freeze
107
115
  end
108
116
 
109
117
  def find_target_files(paths)
@@ -158,11 +166,12 @@ module RuboCop
158
166
 
159
167
  def inspect_files(files) # rubocop:disable Metrics/AbcSize
160
168
  formatter_set.started(files)
169
+ formatters_started = true
161
170
  file_iterator(files) do |file|
162
- offenses = process_file(file)
171
+ offenses, problems = collect_problems { process_file(file) }
163
172
  succeeded = offenses.none? { |o| considered_failure?(o) && offense_displayed?(o) }
164
173
 
165
- [offenses, succeeded]
174
+ [offenses, succeeded, problems]
166
175
  end
167
176
  ensure
168
177
  # OPTIMIZE: Calling `ResultCache.cleanup` takes time. This optimization
@@ -171,17 +180,18 @@ module RuboCop
171
180
  if files.size > 1 && cached_run?
172
181
  ResultCache.cleanup(@config_store, @options[:debug], @options[:cache_root])
173
182
  end
174
- formatter_set.finished(@inspected_files.freeze)
183
+ formatter_set.finished(@inspected_files.freeze) if formatters_started
175
184
  formatter_set.close_output_files
185
+ warn_about_elided_offenses
176
186
  end
177
187
 
178
188
  def file_iterator(files, &block)
179
189
  all_passed = true
180
190
 
181
191
  on_start = ->(file, _index) { file_started(file) }
182
- on_finish = lambda do |file, index, (offenses, passed)|
192
+ on_finish = lambda do |file, index, (offenses, passed, problems)|
183
193
  all_passed &&= passed
184
- finished_report(file, index, offenses)
194
+ finished_report(file, index, offenses, problems)
185
195
  end
186
196
 
187
197
  if run_in_parallel?(files)
@@ -195,8 +205,8 @@ module RuboCop
195
205
  all_passed
196
206
  end
197
207
 
198
- def finished_report(file, index, offenses)
199
- @report_queue[index] = [file, offenses]
208
+ def finished_report(file, index, offenses, problems)
209
+ @report_queue[index] = [file, offenses, problems]
200
210
  @next_index_to_report ||= 0
201
211
  while @report_queue.key?(@next_index_to_report)
202
212
  process_report_queue_entry(@next_index_to_report)
@@ -205,7 +215,10 @@ module RuboCop
205
215
  end
206
216
 
207
217
  def process_report_queue_entry(index)
208
- file, offenses = @report_queue.delete(index)
218
+ file, offenses, (errors, warnings) = @report_queue.delete(index)
219
+ @errors.concat(errors)
220
+ @warnings.concat(warnings)
221
+ file_problems(file, errors, warnings) unless errors.empty? && warnings.empty?
209
222
  file_finished(file, offenses)
210
223
  end
211
224
 
@@ -269,6 +282,21 @@ module RuboCop
269
282
  paths.each { |path| puts PathUtil.relative_path(path) }
270
283
  end
271
284
 
285
+ # Keeps the errors and warnings a file causes apart from the rest of the
286
+ # run's. A parallel worker inspects files in a copy of the runner that is
287
+ # thrown away, so they have to travel back with the file's offenses.
288
+ def collect_problems
289
+ errors = @errors
290
+ warnings = @warnings
291
+ @errors = []
292
+ @warnings = []
293
+
294
+ [yield, [@errors, @warnings]]
295
+ ensure
296
+ @errors = errors
297
+ @warnings = warnings
298
+ end
299
+
272
300
  def process_file(file)
273
301
  file_offenses(file)
274
302
  rescue InfiniteCorrectionLoop => e
@@ -321,9 +349,11 @@ module RuboCop
321
349
  if redundant_updated
322
350
  # Do one extra inspection loop if any redundant disables were
323
351
  # removed. This is done in order to find rubocop:enable directives that
324
- # have now become useless.
325
- _source, new_offenses = do_inspection_loop(file)
326
- offenses |= new_offenses
352
+ # have now become useless. It finds the offenses that are left again,
353
+ # located in the corrected source, so only the corrected ones are kept
354
+ # from before, as `inspect_and_correct` does.
355
+ _source, new_offenses = do_inspection_loop(file, team.updated_source)
356
+ offenses = offenses.select(&:corrected?) | new_offenses
327
357
  end
328
358
  end
329
359
  offenses
@@ -336,6 +366,8 @@ module RuboCop
336
366
  team = Cop::Team.mobilize([Cop::Lint::RedundantCopDisableDirective], config, @options)
337
367
  return if team.cops.empty?
338
368
 
369
+ team.defer_corrections = @options[:diff]
370
+
339
371
  team.cops.first.offenses_to_check = offenses
340
372
  yield team
341
373
  end
@@ -369,6 +401,15 @@ module RuboCop
369
401
  formatter_set.file_started(file, cli_options: @options, config_store: @config_store)
370
402
  end
371
403
 
404
+ # Formatters get the messages without the colors added for the terminal.
405
+ def file_problems(file, errors, warnings)
406
+ formatter_set.file_problems(file, plain_messages(errors), plain_messages(warnings))
407
+ end
408
+
409
+ def plain_messages(problems)
410
+ problems.map { |problem| Rainbow::StringUtils.uncolor(problem.to_s) }
411
+ end
412
+
372
413
  def file_finished(file, offenses)
373
414
  @inspected_files << file
374
415
  offenses = offenses_to_report(offenses)
@@ -387,15 +428,15 @@ module RuboCop
387
428
  return unless cache
388
429
  # Caching results when a cop has crashed would prevent the crash in the
389
430
  # next run, since the cop would not be called then. We want crashes to
390
- # show up the same in each run.
431
+ # show up the same in each run. Only this file's errors and warnings are
432
+ # visible here, see `collect_problems`.
391
433
  return if errors.any? || warnings.any?
392
434
 
393
435
  cache.save(offenses)
394
436
  end
395
437
 
396
- def do_inspection_loop(file)
397
- # We can reuse the prism result since the source did not change yet.
398
- processed_source = get_processed_source(file, @prism_result)
438
+ def do_inspection_loop(file, source = nil)
439
+ processed_source = initial_processed_source(file, source)
399
440
  original_source = processed_source.raw_source
400
441
  # This variable is 2d array used to track corrected offenses after each
401
442
  # inspection iteration. This is used to output meaningful infinite loop
@@ -428,6 +469,15 @@ module RuboCop
428
469
  finalize_corrections(file, original_source, corrected_source)
429
470
  end
430
471
 
472
+ # `source` is the corrected source to continue from when the corrections
473
+ # are kept in memory rather than written to the file (`--diff`).
474
+ def initial_processed_source(file, source)
475
+ return get_processed_source(file, nil, source: source) if source
476
+
477
+ # We can reuse the prism result since the source did not change yet.
478
+ get_processed_source(file, @prism_result)
479
+ end
480
+
431
481
  def inspect_and_correct(processed_source, offenses_by_iteration)
432
482
  # The offenses that couldn't be corrected will be found again so we
433
483
  # only keep the corrected ones in order to avoid duplicate reporting.
@@ -445,24 +495,26 @@ module RuboCop
445
495
  if @options[:diff]
446
496
  record_diff(file, original_source, corrected_source)
447
497
  elsif corrected_source
448
- File.write(file, corrected_source)
498
+ Util.replace_file_contents(file, corrected_source)
449
499
  end
450
500
  end
451
501
 
452
502
  # `--diff` reports what autocorrection would do instead of doing it. With
453
503
  # `--stdin` the corrected source is handed back through the options rather
454
- # than deferred, so that is where the new source comes from.
504
+ # than deferred, so that is where the new source comes from. A file can go
505
+ # through more than one inspection loop, so the diff runs from the source
506
+ # the first loop started with to the one the last loop ended with.
455
507
  def record_diff(file, original_source, corrected_source)
456
- new_source = @options[:stdin] || corrected_source
457
- return unless original_source && new_source
508
+ return unless original_source
458
509
 
510
+ new_source = @options[:stdin] || corrected_source
459
511
  # The original source is the file as it is on disk, so the corrected one
460
512
  # has to go through the same line ending conversion `File.write` would
461
513
  # apply, or on Windows every single line reads as changed.
462
- new_source = emulate_write_read_cycle(new_source)
514
+ new_source = new_source ? Util.emulate_write_read_cycle(new_source) : original_source
463
515
 
464
- diff = UnifiedDiff.new(PathUtil.smart_path(file), original_source, new_source).to_s
465
- @diffs << diff unless diff.empty?
516
+ sources = (@diff_sources[file] ||= [original_source])
517
+ sources[1] = new_source
466
518
  end
467
519
 
468
520
  def inspect_iteration(processed_source)
@@ -472,17 +524,6 @@ module RuboCop
472
524
  [team, offenses, updated_source_file]
473
525
  end
474
526
 
475
- # When corrections were written to disk and read back between iterations,
476
- # the text-mode write converted LF to CRLF on Windows, and cops like
477
- # `Layout/EndOfLine` rely on seeing the source as it would be on disk.
478
- # Apply the same conversion to the in-memory source. The final `File.write`
479
- # still performs it for the file itself.
480
- def emulate_write_read_cycle(source)
481
- return source unless Platform.windows?
482
-
483
- source.encode(source.encoding, crlf_newline: true)
484
- end
485
-
486
527
  # Custom ruby extractors may derive their fragments from the file on
487
528
  # disk rather than from the passed processed source, so corrections can
488
529
  # only be kept in memory when the default extractor is used.
@@ -573,6 +614,12 @@ module RuboCop
573
614
  end
574
615
  end
575
616
 
617
+ # Lets a runner that is reused from run to run pick up a different `--only`
618
+ # or `--except`.
619
+ def forget_mobilized_cops
620
+ @mobilized_cop_classes = nil
621
+ end
622
+
576
623
  def mobilize_cop_badge?(badge, config)
577
624
  return false if badge.department == :Test
578
625
  return false if badge.match_name?(@options[:except])
@@ -589,7 +636,7 @@ module RuboCop
589
636
  %i[only except].each do |option|
590
637
  next unless @options[option]
591
638
 
592
- @options[option].map! do |cop_name|
639
+ @options[option] = @options[option].map do |cop_name|
593
640
  Cop::Registry.qualified_cop_name(cop_name, "--#{option} option")
594
641
  end
595
642
  end
@@ -630,7 +677,28 @@ module RuboCop
630
677
  end
631
678
 
632
679
  def offenses_to_report(offenses)
633
- offenses.select { |o| offense_displayed?(o) }
680
+ offenses = offenses.select { |o| offense_displayed?(o) }
681
+ offenses = offense_limit.filter(offenses) if offense_limit
682
+ offenses
683
+ end
684
+
685
+ # Files are reported in order, so which offenses survive the limit does not
686
+ # depend on how the run was parallelized.
687
+ def offense_limit
688
+ return @offense_limit if defined?(@offense_limit)
689
+
690
+ max = @options[:max_offenses_per_cop]
691
+ @offense_limit = (OffenseLimit.new(max.to_i) if max)
692
+ end
693
+
694
+ def warn_about_elided_offenses
695
+ elided = offense_limit&.elided_per_cop
696
+ return unless elided&.any?
697
+
698
+ warn Rainbow(
699
+ "#{elided.values.sum} more offenses were not reported because of " \
700
+ "--max-offenses-per-cop=#{offense_limit.max} (#{elided.keys.join(', ')})."
701
+ ).yellow
634
702
  end
635
703
 
636
704
  def supports_safe_autocorrect?(offense)
@@ -662,7 +730,7 @@ module RuboCop
662
730
 
663
731
  processed_source = if source
664
732
  ProcessedSource.new(
665
- emulate_write_read_cycle(source),
733
+ Util.emulate_write_read_cycle(source),
666
734
  ruby_version,
667
735
  file,
668
736
  parser_engine: parser_engine
@@ -112,8 +112,8 @@ module RuboCop
112
112
 
113
113
  def pid_running?
114
114
  Process.kill(0, pid_path(create_dir: false).read.to_i) == 1
115
- rescue Errno::ESRCH, Errno::ENOENT, Errno::EACCES, Errno::EROFS, Errno::ENAMETOOLONG,
116
- Errno::ENOTDIR
115
+ rescue Errno::ESRCH, Errno::ENOENT, Errno::EACCES, Errno::EPERM, Errno::EROFS,
116
+ Errno::ENAMETOOLONG, Errno::ENOTDIR
117
117
  false
118
118
  end
119
119
 
data/lib/rubocop/util.rb CHANGED
@@ -12,5 +12,74 @@ module RuboCop
12
12
  ensure
13
13
  $VERBOSE = old_verbose
14
14
  end
15
+
16
+ # The source as it reads back after `File.write`, which in text mode turns
17
+ # each LF into CRLF on Windows. Cops like `Layout/EndOfLine` have to see
18
+ # corrected code that way between correction passes, and so does anything
19
+ # that compares it with what a write leaves on disk. The final `File.write`
20
+ # still performs the conversion for the file itself.
21
+ def self.emulate_write_read_cycle(source)
22
+ return source unless Platform.windows?
23
+
24
+ source.encode(source.encoding, crlf_newline: true)
25
+ end
26
+
27
+ # Replaces the contents of `path` without a window in which the file is truncated
28
+ # but not yet rewritten, so that an interrupted run or a full disk cannot lose
29
+ # the original: the new contents are written next to the file and renamed over it,
30
+ # like `ResultCache#save`. `File.write` is used for the temporary file so text-mode
31
+ # semantics (the CRLF conversion on Windows) stay identical to a direct write.
32
+ # Where the temporary file cannot be created or renamed for lack of permission,
33
+ # a direct write is used as before.
34
+ def self.replace_file_contents(path, contents)
35
+ # Write through symlinks so that the rename replaces the real file and
36
+ # the symlink survives.
37
+ path = File.realpath(path)
38
+
39
+ # Opening a read-only file fails without truncating it, keeping the behavior
40
+ # a direct write always had.
41
+ return File.write(path, contents) unless File.writable?(path)
42
+
43
+ temp_path = "#{path}.#{Process.pid}.#{rand(1_000_000_000)}.rubocop.tmp"
44
+ begin
45
+ replace_via_temporary_file(path, temp_path, contents)
46
+ ensure
47
+ FileUtils.rm_f(temp_path)
48
+ end
49
+ rescue Errno::ENOENT
50
+ # The file vanished mid-run or is a dangling symlink: recreate it the way
51
+ # a direct write always has.
52
+ File.write(path, contents)
53
+ end
54
+
55
+ def self.replace_via_temporary_file(path, temp_path, contents)
56
+ # The directory refuses new files even though the file itself is writable,
57
+ # so a direct write is the only option left.
58
+ return File.write(path, contents) unless write_temporary_file(temp_path, contents)
59
+
60
+ begin
61
+ # A fresh file would get umask-default permissions on rename;
62
+ # keep the original's.
63
+ File.chmod(File.stat(path).mode, temp_path)
64
+ File.rename(temp_path, path)
65
+ rescue SystemCallError
66
+ # The replacement was refused, e.g. the file is open in another process on Windows.
67
+ # The contents already fit on disk, so a direct write is safe once the temporary copy
68
+ # no longer takes up that space.
69
+ FileUtils.rm_f(temp_path)
70
+ File.write(path, contents)
71
+ end
72
+ end
73
+ private_class_method :replace_via_temporary_file
74
+
75
+ # Space errors such as `ENOSPC` must propagate: a direct write would then
76
+ # truncate the file and fail, which is exactly the loss being avoided.
77
+ def self.write_temporary_file(temp_path, contents)
78
+ File.write(temp_path, contents)
79
+ true
80
+ rescue Errno::EACCES, Errno::EPERM
81
+ false
82
+ end
83
+ private_class_method :write_temporary_file
15
84
  end
16
85
  end
@@ -3,7 +3,7 @@
3
3
  module RuboCop
4
4
  # This module holds the RuboCop version information.
5
5
  module Version
6
- STRING = '1.91.0'
6
+ STRING = '1.92.0'
7
7
 
8
8
  MSG = '%<version>s (using %<parser_version>s, ' \
9
9
  'rubocop-ast %<rubocop_ast_version>s, ' \
data/lib/rubocop.rb CHANGED
@@ -55,6 +55,8 @@ require_relative 'rubocop/cop/base'
55
55
  require_relative 'rubocop/cop/cop'
56
56
  require_relative 'rubocop/cop/commissioner'
57
57
  require_relative 'rubocop/cop/documentation'
58
+ require_relative 'rubocop/cop/cop_documentation'
59
+ require_relative 'rubocop/cop/cop_explanation'
58
60
  require_relative 'rubocop/cop/corrector'
59
61
  require_relative 'rubocop/cop/correctors'
60
62
  require_relative 'rubocop/cop/force'
@@ -117,12 +119,15 @@ require_relative 'rubocop/comment_config/disable_next'
117
119
  require_relative 'rubocop/comment_config/push_pop'
118
120
  require_relative 'rubocop/comment_config'
119
121
  require_relative 'rubocop/result_cache'
122
+ require_relative 'rubocop/offense_limit'
120
123
  require_relative 'rubocop/runner'
121
124
  require_relative 'rubocop/cli'
122
125
  require_relative 'rubocop/cli/command'
123
126
  require_relative 'rubocop/cli/environment'
124
127
  require_relative 'rubocop/cli/command/base'
125
128
  require_relative 'rubocop/cli/command/auto_generate_config'
129
+ require_relative 'rubocop/cli/command/cop_names'
130
+ require_relative 'rubocop/cli/command/explain'
126
131
  require_relative 'rubocop/cli/command/execute_runner'
127
132
  require_relative 'rubocop/cli/command/init_dotfile'
128
133
  require_relative 'rubocop/cli/command/lsp'
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rubocop
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.91.0
4
+ version: 1.92.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Bozhidar Batsov
@@ -107,7 +107,7 @@ dependencies:
107
107
  requirements:
108
108
  - - ">="
109
109
  - !ruby/object:Gem::Version
110
- version: 2.9.3
110
+ version: 2.11.3
111
111
  - - "<"
112
112
  - !ruby/object:Gem::Version
113
113
  version: '3.0'
@@ -117,7 +117,7 @@ dependencies:
117
117
  requirements:
118
118
  - - ">="
119
119
  - !ruby/object:Gem::Version
120
- version: 2.9.3
120
+ version: 2.11.3
121
121
  - - "<"
122
122
  - !ruby/object:Gem::Version
123
123
  version: '3.0'
@@ -206,7 +206,9 @@ files:
206
206
  - lib/rubocop/cli/command.rb
207
207
  - lib/rubocop/cli/command/auto_generate_config.rb
208
208
  - lib/rubocop/cli/command/base.rb
209
+ - lib/rubocop/cli/command/cop_names.rb
209
210
  - lib/rubocop/cli/command/execute_runner.rb
211
+ - lib/rubocop/cli/command/explain.rb
210
212
  - lib/rubocop/cli/command/init_dotfile.rb
211
213
  - lib/rubocop/cli/command/list_enabled_cops_for.rb
212
214
  - lib/rubocop/cli/command/lsp.rb
@@ -250,6 +252,8 @@ files:
250
252
  - lib/rubocop/cop/bundler/ordered_gems.rb
251
253
  - lib/rubocop/cop/commissioner.rb
252
254
  - lib/rubocop/cop/cop.rb
255
+ - lib/rubocop/cop/cop_documentation.rb
256
+ - lib/rubocop/cop/cop_explanation.rb
253
257
  - lib/rubocop/cop/corrector.rb
254
258
  - lib/rubocop/cop/correctors.rb
255
259
  - lib/rubocop/cop/correctors/alignment_corrector.rb
@@ -643,6 +647,7 @@ files:
643
647
  - lib/rubocop/cop/mixin/empty_lines_around_body.rb
644
648
  - lib/rubocop/cop/mixin/empty_parameter.rb
645
649
  - lib/rubocop/cop/mixin/end_keyword_alignment.rb
650
+ - lib/rubocop/cop/mixin/endless_method_body_help.rb
646
651
  - lib/rubocop/cop/mixin/endless_method_rewriter.rb
647
652
  - lib/rubocop/cop/mixin/enforce_superclass.rb
648
653
  - lib/rubocop/cop/mixin/first_element_line_break.rb
@@ -675,6 +680,7 @@ files:
675
680
  - lib/rubocop/cop/mixin/on_normal_if_unless.rb
676
681
  - lib/rubocop/cop/mixin/ordered_gem_node.rb
677
682
  - lib/rubocop/cop/mixin/parentheses.rb
683
+ - lib/rubocop/cop/mixin/pattern_matching_help.rb
678
684
  - lib/rubocop/cop/mixin/percent_array.rb
679
685
  - lib/rubocop/cop/mixin/percent_literal.rb
680
686
  - lib/rubocop/cop/mixin/preceding_following_alignment.rb
@@ -1091,8 +1097,13 @@ files:
1091
1097
  - lib/rubocop/lsp/severity.rb
1092
1098
  - lib/rubocop/lsp/stdin_runner.rb
1093
1099
  - lib/rubocop/magic_comment.rb
1100
+ - lib/rubocop/mcp/autocorrection_request.rb
1101
+ - lib/rubocop/mcp/explain_tool.rb
1102
+ - lib/rubocop/mcp/scope.rb
1094
1103
  - lib/rubocop/mcp/server.rb
1104
+ - lib/rubocop/mcp/source_files.rb
1095
1105
  - lib/rubocop/name_similarity.rb
1106
+ - lib/rubocop/offense_limit.rb
1096
1107
  - lib/rubocop/options.rb
1097
1108
  - lib/rubocop/path_util.rb
1098
1109
  - lib/rubocop/pending_cops_reporter.rb
@@ -1146,9 +1157,9 @@ licenses:
1146
1157
  - MIT
1147
1158
  metadata:
1148
1159
  homepage_uri: https://rubocop.org/
1149
- changelog_uri: https://github.com/rubocop/rubocop/releases/tag/v1.91.0
1160
+ changelog_uri: https://github.com/rubocop/rubocop/releases/tag/v1.92.0
1150
1161
  source_code_uri: https://github.com/rubocop/rubocop/
1151
- documentation_uri: https://docs.rubocop.org/rubocop/1.91/
1162
+ documentation_uri: https://docs.rubocop.org/rubocop/1.92/
1152
1163
  bug_tracker_uri: https://github.com/rubocop/rubocop/issues
1153
1164
  rubygems_mfa_required: 'true'
1154
1165
  rdoc_options: []