rubocop 1.91.0 → 1.92.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 (278) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  3. data/config/default.yml +4 -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.rb +0 -4
  14. data/lib/rubocop/config_loader.rb +6 -3
  15. data/lib/rubocop/config_loader_resolver.rb +37 -12
  16. data/lib/rubocop/config_obsoletion/removed_cop.rb +1 -1
  17. data/lib/rubocop/config_obsoletion.rb +9 -0
  18. data/lib/rubocop/config_validator.rb +11 -0
  19. data/lib/rubocop/cop/autocorrect_logic.rb +10 -5
  20. data/lib/rubocop/cop/badge.rb +1 -1
  21. data/lib/rubocop/cop/base.rb +42 -21
  22. data/lib/rubocop/cop/bundler/gem_comment.rb +0 -1
  23. data/lib/rubocop/cop/bundler/ordered_gems.rb +0 -8
  24. data/lib/rubocop/cop/cop_documentation.rb +81 -0
  25. data/lib/rubocop/cop/cop_explanation.rb +140 -0
  26. data/lib/rubocop/cop/correctors/alignment_corrector.rb +13 -1
  27. data/lib/rubocop/cop/correctors/each_to_for_corrector.rb +37 -7
  28. data/lib/rubocop/cop/correctors/empty_line_corrector.rb +1 -1
  29. data/lib/rubocop/cop/correctors/lambda_literal_to_method_corrector.rb +25 -4
  30. data/lib/rubocop/cop/correctors/multiline_literal_brace_corrector.rb +34 -0
  31. data/lib/rubocop/cop/correctors/parentheses_corrector.rb +45 -8
  32. data/lib/rubocop/cop/documentation.rb +1 -1
  33. data/lib/rubocop/cop/gemspec/dependency_version.rb +0 -6
  34. data/lib/rubocop/cop/gemspec/ordered_dependencies.rb +0 -6
  35. data/lib/rubocop/cop/internal_affairs/node_matcher_directive.rb +2 -5
  36. data/lib/rubocop/cop/internal_affairs/node_type_group.rb +0 -7
  37. data/lib/rubocop/cop/internal_affairs/redundant_context_config_parameter.rb +1 -7
  38. data/lib/rubocop/cop/internal_affairs/style_detected_api_use.rb +1 -7
  39. data/lib/rubocop/cop/layout/argument_alignment.rb +5 -1
  40. data/lib/rubocop/cop/layout/block_alignment.rb +27 -0
  41. data/lib/rubocop/cop/layout/case_indentation.rb +25 -9
  42. data/lib/rubocop/cop/layout/class_structure.rb +29 -7
  43. data/lib/rubocop/cop/layout/def_end_alignment.rb +4 -0
  44. data/lib/rubocop/cop/layout/dot_position.rb +25 -15
  45. data/lib/rubocop/cop/layout/empty_line_after_multiline_condition.rb +21 -8
  46. data/lib/rubocop/cop/layout/empty_lines.rb +5 -2
  47. data/lib/rubocop/cop/layout/empty_lines_after_module_inclusion.rb +1 -1
  48. data/lib/rubocop/cop/layout/empty_lines_around_attribute_accessor.rb +1 -0
  49. data/lib/rubocop/cop/layout/empty_lines_around_block_body.rb +9 -3
  50. data/lib/rubocop/cop/layout/empty_lines_around_exception_handling_keywords.rb +23 -1
  51. data/lib/rubocop/cop/layout/end_alignment.rb +21 -25
  52. data/lib/rubocop/cop/layout/extra_spacing.rb +21 -6
  53. data/lib/rubocop/cop/layout/first_argument_indentation.rb +27 -1
  54. data/lib/rubocop/cop/layout/first_array_element_line_break.rb +15 -0
  55. data/lib/rubocop/cop/layout/first_hash_element_indentation.rb +16 -8
  56. data/lib/rubocop/cop/layout/first_method_argument_line_break.rb +6 -0
  57. data/lib/rubocop/cop/layout/first_method_parameter_line_break.rb +4 -0
  58. data/lib/rubocop/cop/layout/hash_alignment.rb +16 -12
  59. data/lib/rubocop/cop/layout/heredoc_argument_closing_parenthesis.rb +7 -3
  60. data/lib/rubocop/cop/layout/heredoc_indentation.rb +15 -0
  61. data/lib/rubocop/cop/layout/indentation_consistency.rb +22 -5
  62. data/lib/rubocop/cop/layout/indentation_style.rb +4 -1
  63. data/lib/rubocop/cop/layout/indentation_width.rb +2 -2
  64. data/lib/rubocop/cop/layout/line_continuation_spacing.rb +8 -1
  65. data/lib/rubocop/cop/layout/line_end_string_concatenation_indentation.rb +25 -9
  66. data/lib/rubocop/cop/layout/line_length.rb +176 -26
  67. data/lib/rubocop/cop/layout/multiline_array_brace_layout.rb +12 -0
  68. data/lib/rubocop/cop/layout/multiline_block_layout.rb +8 -1
  69. data/lib/rubocop/cop/layout/multiline_hash_brace_layout.rb +9 -0
  70. data/lib/rubocop/cop/layout/multiline_method_call_brace_layout.rb +6 -0
  71. data/lib/rubocop/cop/layout/multiline_method_call_indentation.rb +30 -5
  72. data/lib/rubocop/cop/layout/multiline_method_definition_brace_layout.rb +4 -0
  73. data/lib/rubocop/cop/layout/redundant_line_break.rb +22 -1
  74. data/lib/rubocop/cop/layout/rescue_ensure_alignment.rb +16 -3
  75. data/lib/rubocop/cop/layout/single_line_block_chain.rb +10 -2
  76. data/lib/rubocop/cop/layout/space_after_not.rb +5 -0
  77. data/lib/rubocop/cop/layout/space_around_block_parameters.rb +6 -11
  78. data/lib/rubocop/cop/layout/space_around_keyword.rb +10 -0
  79. data/lib/rubocop/cop/layout/space_around_operators.rb +31 -4
  80. data/lib/rubocop/cop/layout/space_before_block_braces.rb +16 -1
  81. data/lib/rubocop/cop/layout/space_inside_array_literal_brackets.rb +15 -2
  82. data/lib/rubocop/cop/layout/space_inside_block_braces.rb +32 -9
  83. data/lib/rubocop/cop/layout/space_inside_range_literal.rb +29 -11
  84. data/lib/rubocop/cop/layout/space_inside_reference_brackets.rb +2 -12
  85. data/lib/rubocop/cop/lint/ambiguous_operator.rb +1 -1
  86. data/lib/rubocop/cop/lint/ambiguous_operator_precedence.rb +2 -1
  87. data/lib/rubocop/cop/lint/ambiguous_regexp_literal.rb +6 -1
  88. data/lib/rubocop/cop/lint/empty_conditional_body.rb +3 -1
  89. data/lib/rubocop/cop/lint/heredoc_method_call_position.rb +4 -0
  90. data/lib/rubocop/cop/lint/interpolation_check.rb +20 -17
  91. data/lib/rubocop/cop/lint/literal_as_condition.rb +15 -0
  92. data/lib/rubocop/cop/lint/literal_in_interpolation.rb +63 -19
  93. data/lib/rubocop/cop/lint/loop.rb +24 -2
  94. data/lib/rubocop/cop/lint/misplaced_magic_comment.rb +71 -11
  95. data/lib/rubocop/cop/lint/missing_cop_enable_directive.rb +18 -3
  96. data/lib/rubocop/cop/lint/nested_percent_literal.rb +3 -0
  97. data/lib/rubocop/cop/lint/number_conversion.rb +3 -1
  98. data/lib/rubocop/cop/lint/parentheses_as_grouped_expression.rb +22 -1
  99. data/lib/rubocop/cop/lint/percent_string_array.rb +75 -9
  100. data/lib/rubocop/cop/lint/redundant_cop_disable_directive.rb +39 -9
  101. data/lib/rubocop/cop/lint/redundant_dir_glob_sort.rb +13 -2
  102. data/lib/rubocop/cop/lint/redundant_splat_expansion.rb +11 -4
  103. data/lib/rubocop/cop/lint/safe_navigation_consistency.rb +47 -1
  104. data/lib/rubocop/cop/lint/symbol_conversion.rb +1 -0
  105. data/lib/rubocop/cop/lint/unescaped_bracket_in_regexp.rb +18 -1
  106. data/lib/rubocop/cop/lint/useless_assignment.rb +14 -7
  107. data/lib/rubocop/cop/lint/useless_setter_call.rb +7 -4
  108. data/lib/rubocop/cop/lint/void.rb +8 -1
  109. data/lib/rubocop/cop/metrics/class_length.rb +4 -2
  110. data/lib/rubocop/cop/metrics/cyclomatic_complexity.rb +0 -4
  111. data/lib/rubocop/cop/metrics/module_length.rb +4 -2
  112. data/lib/rubocop/cop/mixin/alignment.rb +4 -0
  113. data/lib/rubocop/cop/mixin/check_line_breakable.rb +23 -19
  114. data/lib/rubocop/cop/mixin/check_single_line_suitability.rb +8 -2
  115. data/lib/rubocop/cop/mixin/code_length.rb +10 -2
  116. data/lib/rubocop/cop/mixin/comments_help.rb +1 -1
  117. data/lib/rubocop/cop/mixin/empty_lines_around_body.rb +12 -1
  118. data/lib/rubocop/cop/mixin/endless_method_body_help.rb +105 -0
  119. data/lib/rubocop/cop/mixin/forbidden_identifiers.rb +2 -2
  120. data/lib/rubocop/cop/mixin/hash_alignment_styles.rb +41 -6
  121. data/lib/rubocop/cop/mixin/hash_shorthand_syntax.rb +26 -2
  122. data/lib/rubocop/cop/mixin/min_branches_count.rb +3 -1
  123. data/lib/rubocop/cop/mixin/multiline_element_line_breaks.rb +11 -1
  124. data/lib/rubocop/cop/mixin/multiline_literal_brace_layout.rb +9 -0
  125. data/lib/rubocop/cop/mixin/ordered_gem_node.rb +1 -1
  126. data/lib/rubocop/cop/mixin/pattern_matching_help.rb +35 -0
  127. data/lib/rubocop/cop/mixin/preceding_following_alignment.rb +17 -2
  128. data/lib/rubocop/cop/mixin/project_index_help.rb +15 -0
  129. data/lib/rubocop/cop/mixin/reparsed_equivalence.rb +13 -3
  130. data/lib/rubocop/cop/mixin/statement_modifier.rb +39 -5
  131. data/lib/rubocop/cop/mixin/trailing_comma.rb +1 -1
  132. data/lib/rubocop/cop/mixin/visibility_help.rb +2 -1
  133. data/lib/rubocop/cop/mixin.rb +2 -0
  134. data/lib/rubocop/cop/naming/accessor_method_name.rb +0 -16
  135. data/lib/rubocop/cop/naming/block_forwarding.rb +1 -1
  136. data/lib/rubocop/cop/naming/heredoc_delimiter_case.rb +9 -2
  137. data/lib/rubocop/cop/naming/heredoc_delimiter_naming.rb +5 -3
  138. data/lib/rubocop/cop/naming/predicate_prefix.rb +7 -22
  139. data/lib/rubocop/cop/naming/rescued_exceptions_variable_name.rb +1 -1
  140. data/lib/rubocop/cop/offense.rb +59 -3
  141. data/lib/rubocop/cop/registry.rb +1 -1
  142. data/lib/rubocop/cop/security/json_load.rb +16 -4
  143. data/lib/rubocop/cop/style/access_modifier_declarations.rb +37 -2
  144. data/lib/rubocop/cop/style/alias.rb +18 -5
  145. data/lib/rubocop/cop/style/ambiguous_endless_method_definition.rb +4 -3
  146. data/lib/rubocop/cop/style/and_or.rb +37 -10
  147. data/lib/rubocop/cop/style/arguments_forwarding.rb +32 -3
  148. data/lib/rubocop/cop/style/attr.rb +3 -4
  149. data/lib/rubocop/cop/style/bitwise_predicate.rb +6 -4
  150. data/lib/rubocop/cop/style/block_delimiters.rb +35 -3
  151. data/lib/rubocop/cop/style/case_equality.rb +14 -3
  152. data/lib/rubocop/cop/style/case_like_if.rb +6 -3
  153. data/lib/rubocop/cop/style/class_and_module_children.rb +57 -16
  154. data/lib/rubocop/cop/style/class_methods_definitions.rb +15 -0
  155. data/lib/rubocop/cop/style/collection_querying.rb +79 -46
  156. data/lib/rubocop/cop/style/combinable_loops.rb +89 -11
  157. data/lib/rubocop/cop/style/concat_array_literals.rb +58 -7
  158. data/lib/rubocop/cop/style/conditional_assignment.rb +4 -0
  159. data/lib/rubocop/cop/style/disable_cops_within_source_code_directive.rb +65 -9
  160. data/lib/rubocop/cop/style/double_negation.rb +11 -0
  161. data/lib/rubocop/cop/style/empty_literal.rb +1 -1
  162. data/lib/rubocop/cop/style/empty_method.rb +3 -1
  163. data/lib/rubocop/cop/style/empty_string_inside_interpolation.rb +53 -11
  164. data/lib/rubocop/cop/style/endless_method.rb +104 -11
  165. data/lib/rubocop/cop/style/exact_regexp_match.rb +45 -8
  166. data/lib/rubocop/cop/style/explicit_block_argument.rb +50 -10
  167. data/lib/rubocop/cop/style/float_division.rb +37 -2
  168. data/lib/rubocop/cop/style/for.rb +53 -2
  169. data/lib/rubocop/cop/style/format_string.rb +28 -6
  170. data/lib/rubocop/cop/style/frozen_string_literal_comment.rb +2 -1
  171. data/lib/rubocop/cop/style/guard_clause.rb +31 -22
  172. data/lib/rubocop/cop/style/hash_as_last_array_item.rb +4 -0
  173. data/lib/rubocop/cop/style/hash_conversion.rb +28 -8
  174. data/lib/rubocop/cop/style/hash_lookup_method.rb +42 -9
  175. data/lib/rubocop/cop/style/hash_syntax.rb +16 -3
  176. data/lib/rubocop/cop/style/identical_conditional_branches.rb +22 -3
  177. data/lib/rubocop/cop/style/if_inside_else.rb +21 -1
  178. data/lib/rubocop/cop/style/if_unless_modifier.rb +32 -26
  179. data/lib/rubocop/cop/style/if_unless_modifier_of_if_unless.rb +4 -0
  180. data/lib/rubocop/cop/style/if_with_semicolon.rb +47 -4
  181. data/lib/rubocop/cop/style/inverse_methods.rb +24 -2
  182. data/lib/rubocop/cop/style/it_block_parameter.rb +94 -4
  183. data/lib/rubocop/cop/style/keyword_parameters_order.rb +3 -1
  184. data/lib/rubocop/cop/style/lambda.rb +34 -1
  185. data/lib/rubocop/cop/style/lambda_call.rb +7 -2
  186. data/lib/rubocop/cop/style/map_into_array.rb +1 -2
  187. data/lib/rubocop/cop/style/method_call_with_args_parentheses/omit_parentheses.rb +80 -13
  188. data/lib/rubocop/cop/style/method_call_with_args_parentheses.rb +26 -2
  189. data/lib/rubocop/cop/style/method_call_without_args_parentheses.rb +26 -3
  190. data/lib/rubocop/cop/style/method_def_parentheses.rb +38 -4
  191. data/lib/rubocop/cop/style/missing_else.rb +7 -1
  192. data/lib/rubocop/cop/style/mixin_grouping.rb +12 -3
  193. data/lib/rubocop/cop/style/multiline_if_modifier.rb +45 -5
  194. data/lib/rubocop/cop/style/multiline_in_pattern_then.rb +7 -0
  195. data/lib/rubocop/cop/style/multiline_method_signature.rb +2 -5
  196. data/lib/rubocop/cop/style/multiline_ternary_operator.rb +7 -3
  197. data/lib/rubocop/cop/style/multiline_when_then.rb +7 -0
  198. data/lib/rubocop/cop/style/mutable_constant.rb +52 -7
  199. data/lib/rubocop/cop/style/negated_if_else_condition.rb +2 -1
  200. data/lib/rubocop/cop/style/negative_array_index.rb +9 -1
  201. data/lib/rubocop/cop/style/nested_modifier.rb +27 -8
  202. data/lib/rubocop/cop/style/nested_ternary_operator.rb +26 -8
  203. data/lib/rubocop/cop/style/next.rb +24 -10
  204. data/lib/rubocop/cop/style/nil_comparison.rb +16 -4
  205. data/lib/rubocop/cop/style/non_nil_check.rb +1 -1
  206. data/lib/rubocop/cop/style/not.rb +10 -5
  207. data/lib/rubocop/cop/style/numeric_predicate.rb +28 -5
  208. data/lib/rubocop/cop/style/one_line_conditional.rb +13 -2
  209. data/lib/rubocop/cop/style/parentheses_around_condition.rb +19 -1
  210. data/lib/rubocop/cop/style/percent_literal_delimiters.rb +14 -3
  211. data/lib/rubocop/cop/style/quoted_symbols.rb +5 -7
  212. data/lib/rubocop/cop/style/raise_args.rb +80 -11
  213. data/lib/rubocop/cop/style/reduce_to_hash.rb +47 -33
  214. data/lib/rubocop/cop/style/redundant_begin.rb +45 -5
  215. data/lib/rubocop/cop/style/redundant_condition.rb +30 -13
  216. data/lib/rubocop/cop/style/redundant_double_splat_hash_braces.rb +6 -0
  217. data/lib/rubocop/cop/style/redundant_exception.rb +27 -11
  218. data/lib/rubocop/cop/style/redundant_fetch_block.rb +9 -1
  219. data/lib/rubocop/cop/style/redundant_freeze.rb +11 -3
  220. data/lib/rubocop/cop/style/redundant_interpolation_unfreeze.rb +24 -1
  221. data/lib/rubocop/cop/style/redundant_parentheses.rb +12 -1
  222. data/lib/rubocop/cop/style/redundant_regexp_argument.rb +7 -0
  223. data/lib/rubocop/cop/style/redundant_regexp_constructor.rb +20 -0
  224. data/lib/rubocop/cop/style/redundant_regexp_escape.rb +4 -0
  225. data/lib/rubocop/cop/style/redundant_self.rb +9 -1
  226. data/lib/rubocop/cop/style/redundant_self_assignment_branch.rb +28 -0
  227. data/lib/rubocop/cop/style/redundant_sort.rb +38 -4
  228. data/lib/rubocop/cop/style/regexp_literal.rb +75 -26
  229. data/lib/rubocop/cop/style/require_order.rb +25 -8
  230. data/lib/rubocop/cop/style/rescue_modifier.rb +11 -9
  231. data/lib/rubocop/cop/style/return_nil.rb +17 -2
  232. data/lib/rubocop/cop/style/return_nil_in_predicate_method_definition.rb +11 -1
  233. data/lib/rubocop/cop/style/sample.rb +9 -8
  234. data/lib/rubocop/cop/style/self_assignment.rb +12 -15
  235. data/lib/rubocop/cop/style/semicolon.rb +16 -5
  236. data/lib/rubocop/cop/style/send_with_literal_method_name.rb +4 -0
  237. data/lib/rubocop/cop/style/single_line_methods.rb +42 -4
  238. data/lib/rubocop/cop/style/stabby_lambda_parentheses.rb +29 -5
  239. data/lib/rubocop/cop/style/string_concatenation.rb +21 -4
  240. data/lib/rubocop/cop/style/struct_inheritance.rb +6 -0
  241. data/lib/rubocop/cop/style/super_arguments.rb +3 -0
  242. data/lib/rubocop/cop/style/symbol_proc.rb +24 -6
  243. data/lib/rubocop/cop/style/ternary_parentheses.rb +23 -1
  244. data/lib/rubocop/cop/style/trailing_underscore_variable.rb +23 -19
  245. data/lib/rubocop/cop/style/while_until_modifier.rb +1 -1
  246. data/lib/rubocop/cop/style/yoda_condition.rb +24 -9
  247. data/lib/rubocop/cop/style/yoda_expression.rb +10 -1
  248. data/lib/rubocop/cop/team.rb +10 -5
  249. data/lib/rubocop/cop/variable_force/assignment.rb +1 -1
  250. data/lib/rubocop/cop/variable_force/variable.rb +14 -1
  251. data/lib/rubocop/formatter/base_formatter.rb +22 -0
  252. data/lib/rubocop/formatter/formatter_set.rb +4 -0
  253. data/lib/rubocop/formatter/json_formatter.rb +71 -1
  254. data/lib/rubocop/formatter/offense_count_formatter.rb +3 -1
  255. data/lib/rubocop/formatter/pacman_formatter.rb +3 -1
  256. data/lib/rubocop/formatter/sarif_formatter.rb +12 -7
  257. data/lib/rubocop/formatter/worst_offenders_formatter.rb +3 -1
  258. data/lib/rubocop/lsp/routes.rb +23 -23
  259. data/lib/rubocop/lsp/runtime.rb +47 -11
  260. data/lib/rubocop/lsp/stdin_runner.rb +11 -5
  261. data/lib/rubocop/lsp.rb +5 -2
  262. data/lib/rubocop/mcp/autocorrection_request.rb +52 -0
  263. data/lib/rubocop/mcp/explain_tool.rb +68 -0
  264. data/lib/rubocop/mcp/scope.rb +65 -0
  265. data/lib/rubocop/mcp/server.rb +184 -76
  266. data/lib/rubocop/mcp/source_files.rb +35 -0
  267. data/lib/rubocop/offense_limit.rb +30 -0
  268. data/lib/rubocop/options.rb +45 -10
  269. data/lib/rubocop/plugin/configuration_integrator.rb +36 -8
  270. data/lib/rubocop/remote_config.rb +4 -1
  271. data/lib/rubocop/result_cache.rb +20 -13
  272. data/lib/rubocop/runner.rb +110 -42
  273. data/lib/rubocop/server/cache.rb +6 -6
  274. data/lib/rubocop/target_ruby.rb +21 -9
  275. data/lib/rubocop/util.rb +69 -0
  276. data/lib/rubocop/version.rb +1 -1
  277. data/lib/rubocop.rb +5 -0
  278. metadata +16 -5
@@ -59,15 +59,22 @@ module RuboCop
59
59
 
60
60
  plugin_config.make_excludes_absolute
61
61
 
62
- # `unset_nil: false` keeps `AllCops` keys declared with a nil value by a plugin
63
- # (e.g. `TargetRailsVersion: ~` of rubocop-rails); `merge_all_cop_settings` above
64
- # carries such keys into each subsequent plugin's config, where `unset_nil: true`
65
- # would delete them from the combined configuration.
66
- ConfigLoader.merge_with_default(plugin_config, plugin_config_path, unset_nil: false)
62
+ merge_plugin_config_with_default(plugin_config, plugin_config_path)
67
63
  end
68
64
  end
69
65
  end
70
66
 
67
+ # `unset_nil: false` keeps `AllCops` keys declared with a nil value by a plugin
68
+ # (e.g. `TargetRailsVersion: ~` of rubocop-rails); `merge_all_cop_settings` carries
69
+ # such keys into each subsequent plugin's config, where `unset_nil: true` would
70
+ # delete them from the combined configuration. Preview is left to be applied when
71
+ # a project configuration is resolved against the defaults.
72
+ def merge_plugin_config_with_default(plugin_config, plugin_config_path)
73
+ ConfigLoader.merge_with_default(
74
+ plugin_config, plugin_config_path, unset_nil: false, resolve_preview: false
75
+ )
76
+ end
77
+
71
78
  def merge_plugin_config_into_all_cops!(rubocop_config, plugin_config)
72
79
  rubocop_config['AllCops'].merge!(plugin_config['AllCops'])
73
80
  end
@@ -98,18 +105,39 @@ module RuboCop
98
105
 
99
106
  case rules.type
100
107
  when :path
101
- [ConfigLoader.load_file(rules.value, check: false), rules.value]
108
+ config = ConfigLoader.load_file(rules.value, check: false)
109
+ warn_about_obsolete_config(plugin, config)
110
+ [config, rules.value]
102
111
  when :object
103
112
  path = plugin.method(:rules).source_location[0]
104
113
  [Config.create(rules.value, path, check: true), path]
105
114
  when :error
106
- plugin_name = plugin.about&.name || plugin.inspect
107
115
  error_message = rules.value.respond_to?(:message) ? rules.value.message : rules.value
108
116
 
109
- raise "Plugin `#{plugin_name}' failed to load with error: #{error_message}"
117
+ raise "Plugin `#{plugin_name(plugin)}' failed to load with error: #{error_message}"
110
118
  end
111
119
  end
112
120
 
121
+ # Plugin configuration is loaded without validation, so an obsolete key in a plugin's
122
+ # defaults used to surface only later, against the user's own file, once the two had
123
+ # been merged. Report it here, while the path is still the plugin's, and name the
124
+ # plugin: the file lives inside an installed gem, so the fix is to report it upstream.
125
+ # It is a warning whatever the rule's severity: a stale plugin is not the user's
126
+ # mistake and must not stop the run.
127
+ def warn_about_obsolete_config(plugin, config)
128
+ messages = ConfigObsoletion.new(config).messages
129
+ return if messages.empty?
130
+
131
+ warn Rainbow(<<~WARNING).yellow
132
+ Warning: obsolete configuration found in the `#{plugin_name(plugin)}` plugin:
133
+ #{messages.join("\n")}
134
+ WARNING
135
+ end
136
+
137
+ def plugin_name(plugin)
138
+ plugin.about&.name || plugin.inspect
139
+ end
140
+
113
141
  # This is how we ensure "first-in wins": plugins can override AllCops settings that are
114
142
  # set by RuboCop's default configuration, but once a plugin sets an AllCop setting, they
115
143
  # have exclusive first-in-wins rights to that setting.
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'net/http'
4
3
  require 'time'
4
+ require 'uri'
5
5
 
6
6
  module RuboCop
7
7
  # Common methods and behaviors for dealing with remote config files.
@@ -45,6 +45,9 @@ module RuboCop
45
45
  def request(uri = @uri, limit = 10, &block)
46
46
  raise ArgumentError, 'HTTP redirect too deep' if limit.zero?
47
47
 
48
+ # Loaded here rather than at the top, since most runs never fetch a remote config.
49
+ require 'net/http'
50
+
48
51
  http = Net::HTTP.new(uri.hostname, uri.port)
49
52
  http.use_ssl = uri.instance_of?(URI::HTTPS)
50
53
 
@@ -59,9 +59,9 @@ module RuboCop
59
59
  puts "Removing the #{remove_count} oldest files from #{rubocop_cache_dir}" if verbose
60
60
  sorted = files.sort_by { |path| File.mtime(path) }
61
61
  remove_files(sorted, remove_count)
62
- rescue Errno::ENOENT
62
+ rescue Errno::ENOENT, Errno::EACCES, Errno::EPERM, Errno::EROFS
63
63
  # This can happen if parallel RuboCop invocations try to remove the
64
- # same files. No problem.
64
+ # same files, or if the cache can't be written to. No problem.
65
65
  puts $ERROR_INFO if verbose
66
66
  end
67
67
 
@@ -138,26 +138,17 @@ module RuboCop
138
138
 
139
139
  begin
140
140
  FileUtils.mkdir_p(dir)
141
- rescue Errno::EACCES, Errno::EROFS => e
141
+ rescue Errno::EACCES, Errno::EPERM, Errno::EROFS => e
142
142
  warn "Couldn't create cache directory. Continuing without cache.\n #{e.message}"
143
143
  return
144
144
  end
145
145
 
146
- preliminary_path = "#{@path}_#{rand(1_000_000_000)}"
147
146
  # RuboCop must be in control of where its cached data is stored. A
148
147
  # symbolic link anywhere in the cache directory tree can be an
149
148
  # indication that a symlink attack is being waged.
150
149
  return if symlink_protection_triggered?(dir)
151
150
 
152
- File.open(preliminary_path, 'w', encoding: Encoding::UTF_8) do |f|
153
- f.write(@cached_data.to_json(offenses))
154
- end
155
- # The preliminary path is used so that if there are multiple RuboCop
156
- # processes trying to save data for the same inspected file
157
- # simultaneously, the only problem we run in to is a competition who gets
158
- # to write to the final file. The contents are the same, so no corruption
159
- # of data should occur.
160
- FileUtils.mv(preliminary_path, @path)
151
+ write_cache_file(offenses)
161
152
  end
162
153
 
163
154
  private
@@ -177,6 +168,22 @@ module RuboCop
177
168
  false
178
169
  end
179
170
 
171
+ def write_cache_file(offenses)
172
+ preliminary_path = "#{@path}_#{rand(1_000_000_000)}"
173
+ File.open(preliminary_path, 'w', encoding: Encoding::UTF_8) do |f|
174
+ f.write(@cached_data.to_json(offenses))
175
+ end
176
+ # The preliminary path is used so that if there are multiple RuboCop
177
+ # processes trying to save data for the same inspected file
178
+ # simultaneously, the only problem we run in to is a competition who gets
179
+ # to write to the final file. The contents are the same, so no corruption
180
+ # of data should occur.
181
+ FileUtils.mv(preliminary_path, @path)
182
+ rescue Errno::EACCES, Errno::EPERM, Errno::EROFS => e
183
+ FileUtils.rm_f(preliminary_path)
184
+ warn "Couldn't write cache file. Continuing without cache.\n #{e.message}"
185
+ end
186
+
180
187
  def file_checksum(file, config_store)
181
188
  stat = File.stat(file)
182
189
  return unavailable_checksum unless stat.file?
@@ -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,7 +366,9 @@ module RuboCop
336
366
  team = Cop::Team.mobilize([Cop::Lint::RedundantCopDisableDirective], config, @options)
337
367
  return if team.cops.empty?
338
368
 
339
- team.cops.first.offenses_to_check = offenses
369
+ team.defer_corrections = @options[:diff]
370
+ team.cops.first.offenses_to_check = offenses.reject(&:corrected?)
371
+
340
372
  yield team
341
373
  end
342
374
 
@@ -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
@@ -51,9 +51,9 @@ module RuboCop
51
51
  Pathname(project_dir).join(lockfile_name)
52
52
  end.find(&:exist?)
53
53
  version_data = lockfile_path&.read || RuboCop::Version::STRING
54
- config_data = Pathname(
55
- args_config_file_path || ConfigFinder.find_config_path(Dir.pwd)
56
- ).read
54
+ config_path = Pathname(args_config_file_path || ConfigFinder.find_config_path(Dir.pwd))
55
+ # NOTE: A missing config file is reported by `RuboCop::CLI` with a friendly message.
56
+ config_data = config_path.exist? ? config_path.read : ''
57
57
  yaml = load_erb_templated_yaml(config_data)
58
58
 
59
59
  inherit_from_data = inherit_from_data(yaml)
@@ -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
 
@@ -181,7 +181,7 @@ module RuboCop
181
181
  yaml_code = ERB.new(content).result
182
182
 
183
183
  require 'yaml'
184
- YAML.safe_load(yaml_code, permitted_classes: [Regexp, Symbol], aliases: true)
184
+ YAML.safe_load(yaml_code, permitted_classes: [Regexp, Symbol], aliases: true) || {}
185
185
  end
186
186
  end
187
187
  end
@@ -100,15 +100,23 @@ module RuboCop
100
100
  def gemspec_filepath
101
101
  return @gemspec_filepath if defined?(@gemspec_filepath)
102
102
 
103
- @gemspec_filepath =
104
- @config.traverse_directories_upwards(@config.base_dir_for_path_parameters) do |dir|
105
- # NOTE: Can't use `dir.glob` because of JRuby 9.4.8.0 incompatibility:
106
- # https://github.com/jruby/jruby/issues/8358
107
- candidates = Pathname.glob("#{dir}/*.gemspec")
108
- # Bundler will use a gemspec whatever the filename is, as long as its the only one in
109
- # the folder.
110
- break candidates.first if candidates.one?
111
- end
103
+ @gemspec_filepath = @config.traverse_directories_upwards(
104
+ @config.base_dir_for_path_parameters, ConfigFinder.project_root
105
+ ) do |dir|
106
+ candidates = gemspec_candidates(dir)
107
+
108
+ # Bundler will use a gemspec whatever the filename is, as long as its the only one in
109
+ # the folder.
110
+ break candidates.first if candidates.one?
111
+ end
112
+ end
113
+
114
+ def gemspec_candidates(dir)
115
+ # NOTE: Can't use `dir.glob` because of JRuby 9.4.8.0 incompatibility:
116
+ # https://github.com/jruby/jruby/issues/8358
117
+ Pathname.glob("#{dir}/*.gemspec")
118
+ rescue SystemCallError
119
+ []
112
120
  end
113
121
 
114
122
  def version_from_gemspec_file(file)
@@ -179,6 +187,8 @@ module RuboCop
179
187
  return unless file && File.file?(file)
180
188
 
181
189
  File.read(file).match(pattern) { |md| md[:version].to_f }
190
+ rescue SystemCallError
191
+ nil
182
192
  end
183
193
 
184
194
  def version_file
@@ -250,6 +260,8 @@ module RuboCop
250
260
  result = line.match(/^\s*ruby\s+(\d+\.\d+)[p.\d]*\s*$/)
251
261
  return result.captures.first.to_f if result
252
262
  end
263
+ rescue SystemCallError
264
+ nil
253
265
  end
254
266
 
255
267
  def bundler_lock_file_path
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.2'
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'