rubocop 1.90.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 (303) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  3. data/config/default.yml +241 -11
  4. data/config/obsoletion.yml +9 -0
  5. data/lib/rubocop/cached_data.rb +47 -7
  6. data/lib/rubocop/changed_files.rb +134 -0
  7. data/lib/rubocop/cli/command/cop_names.rb +57 -0
  8. data/lib/rubocop/cli/command/execute_runner.rb +23 -3
  9. data/lib/rubocop/cli/command/explain.rb +34 -0
  10. data/lib/rubocop/cli/command/show_cops.rb +6 -0
  11. data/lib/rubocop/cli/command/show_docs_url.rb +6 -4
  12. data/lib/rubocop/cli.rb +11 -4
  13. data/lib/rubocop/comment_config/directive_range.rb +3 -1
  14. data/lib/rubocop/comment_config/disable_next.rb +49 -3
  15. data/lib/rubocop/comment_config/push_pop.rb +55 -0
  16. data/lib/rubocop/comment_config.rb +32 -53
  17. data/lib/rubocop/config.rb +10 -0
  18. data/lib/rubocop/config_loader.rb +18 -7
  19. data/lib/rubocop/config_loader_resolver.rb +79 -12
  20. data/lib/rubocop/config_obsoletion/cop_rule.rb +2 -1
  21. data/lib/rubocop/config_obsoletion/removed_cop.rb +9 -1
  22. data/lib/rubocop/config_obsoletion.rb +1 -1
  23. data/lib/rubocop/config_validator.rb +29 -3
  24. data/lib/rubocop/cop/autocorrect_logic.rb +10 -5
  25. data/lib/rubocop/cop/badge.rb +1 -1
  26. data/lib/rubocop/cop/base.rb +57 -24
  27. data/lib/rubocop/cop/bundler/gem_comment.rb +0 -1
  28. data/lib/rubocop/cop/bundler/ordered_gems.rb +0 -8
  29. data/lib/rubocop/cop/cop_documentation.rb +81 -0
  30. data/lib/rubocop/cop/cop_explanation.rb +140 -0
  31. data/lib/rubocop/cop/correctors/alignment_corrector.rb +13 -1
  32. data/lib/rubocop/cop/correctors/each_to_for_corrector.rb +22 -7
  33. data/lib/rubocop/cop/correctors/empty_line_corrector.rb +1 -1
  34. data/lib/rubocop/cop/correctors/lambda_literal_to_method_corrector.rb +23 -2
  35. data/lib/rubocop/cop/correctors/parentheses_corrector.rb +45 -8
  36. data/lib/rubocop/cop/correctors.rb +2 -2
  37. data/lib/rubocop/cop/documentation.rb +1 -1
  38. data/lib/rubocop/cop/gemspec/dependency_version.rb +0 -6
  39. data/lib/rubocop/cop/gemspec/ordered_dependencies.rb +0 -6
  40. data/lib/rubocop/cop/gemspec/require_mfa.rb +1 -1
  41. data/lib/rubocop/cop/generator.rb +1 -1
  42. data/lib/rubocop/cop/internal_affairs/node_matcher_directive.rb +2 -5
  43. data/lib/rubocop/cop/internal_affairs/node_type_group.rb +0 -7
  44. data/lib/rubocop/cop/internal_affairs/on_send_without_on_csend.rb +1 -1
  45. data/lib/rubocop/cop/internal_affairs/redundant_context_config_parameter.rb +1 -7
  46. data/lib/rubocop/cop/internal_affairs/style_detected_api_use.rb +1 -7
  47. data/lib/rubocop/cop/layout/argument_alignment.rb +2 -0
  48. data/lib/rubocop/cop/layout/block_alignment.rb +27 -0
  49. data/lib/rubocop/cop/layout/case_indentation.rb +25 -9
  50. data/lib/rubocop/cop/layout/class_structure.rb +9 -1
  51. data/lib/rubocop/cop/layout/comment_indentation.rb +8 -5
  52. data/lib/rubocop/cop/layout/def_end_alignment.rb +4 -0
  53. data/lib/rubocop/cop/layout/else_alignment.rb +1 -0
  54. data/lib/rubocop/cop/layout/empty_line_after_multiline_condition.rb +14 -5
  55. data/lib/rubocop/cop/layout/empty_lines.rb +5 -2
  56. data/lib/rubocop/cop/layout/empty_lines_after_module_inclusion.rb +1 -1
  57. data/lib/rubocop/cop/layout/empty_lines_around_attribute_accessor.rb +1 -0
  58. data/lib/rubocop/cop/layout/empty_lines_around_block_body.rb +1 -1
  59. data/lib/rubocop/cop/layout/empty_lines_around_exception_handling_keywords.rb +23 -1
  60. data/lib/rubocop/cop/layout/end_alignment.rb +16 -10
  61. data/lib/rubocop/cop/layout/extra_spacing.rb +4 -0
  62. data/lib/rubocop/cop/layout/first_argument_indentation.rb +1 -0
  63. data/lib/rubocop/cop/layout/first_array_element_indentation.rb +17 -4
  64. data/lib/rubocop/cop/layout/first_hash_element_indentation.rb +5 -3
  65. data/lib/rubocop/cop/layout/first_method_argument_line_break.rb +6 -0
  66. data/lib/rubocop/cop/layout/first_method_parameter_line_break.rb +4 -0
  67. data/lib/rubocop/cop/layout/first_parameter_indentation.rb +11 -0
  68. data/lib/rubocop/cop/layout/hash_alignment.rb +15 -10
  69. data/lib/rubocop/cop/layout/heredoc_argument_closing_parenthesis.rb +2 -0
  70. data/lib/rubocop/cop/layout/heredoc_indentation.rb +15 -0
  71. data/lib/rubocop/cop/layout/indentation_style.rb +4 -1
  72. data/lib/rubocop/cop/layout/indentation_width.rb +4 -4
  73. data/lib/rubocop/cop/layout/line_continuation_spacing.rb +8 -1
  74. data/lib/rubocop/cop/layout/line_end_string_concatenation_indentation.rb +25 -9
  75. data/lib/rubocop/cop/layout/line_length.rb +71 -19
  76. data/lib/rubocop/cop/layout/multiline_array_brace_layout.rb +12 -0
  77. data/lib/rubocop/cop/layout/multiline_block_layout.rb +8 -1
  78. data/lib/rubocop/cop/layout/multiline_hash_brace_layout.rb +7 -0
  79. data/lib/rubocop/cop/layout/multiline_method_call_brace_layout.rb +6 -0
  80. data/lib/rubocop/cop/layout/multiline_method_call_indentation.rb +14 -0
  81. data/lib/rubocop/cop/layout/multiline_method_definition_brace_layout.rb +4 -0
  82. data/lib/rubocop/cop/layout/redundant_line_break.rb +22 -1
  83. data/lib/rubocop/cop/layout/rescue_ensure_alignment.rb +15 -2
  84. data/lib/rubocop/cop/layout/single_line_block_chain.rb +10 -2
  85. data/lib/rubocop/cop/layout/space_after_not.rb +5 -0
  86. data/lib/rubocop/cop/layout/space_around_block_parameters.rb +10 -4
  87. data/lib/rubocop/cop/layout/space_around_keyword.rb +10 -0
  88. data/lib/rubocop/cop/layout/space_around_operators.rb +5 -1
  89. data/lib/rubocop/cop/layout/space_before_block_braces.rb +4 -1
  90. data/lib/rubocop/cop/layout/space_inside_array_literal_brackets.rb +15 -2
  91. data/lib/rubocop/cop/layout/space_inside_block_braces.rb +31 -8
  92. data/lib/rubocop/cop/lint/ambiguous_operator.rb +1 -1
  93. data/lib/rubocop/cop/lint/ambiguous_operator_precedence.rb +2 -1
  94. data/lib/rubocop/cop/lint/argument_mismatch.rb +14 -4
  95. data/lib/rubocop/cop/lint/assignment_in_condition.rb +2 -0
  96. data/lib/rubocop/cop/lint/cop_directive_syntax.rb +88 -14
  97. data/lib/rubocop/cop/lint/duplicate_methods.rb +1 -1
  98. data/lib/rubocop/cop/lint/empty_block.rb +1 -1
  99. data/lib/rubocop/cop/lint/empty_conditional_body.rb +3 -1
  100. data/lib/rubocop/cop/lint/literal_as_condition.rb +23 -2
  101. data/lib/rubocop/cop/lint/literal_in_interpolation.rb +63 -19
  102. data/lib/rubocop/cop/lint/loop.rb +24 -2
  103. data/lib/rubocop/cop/lint/misplaced_magic_comment.rb +245 -0
  104. data/lib/rubocop/cop/lint/missing_cop_enable_directive.rb +19 -4
  105. data/lib/rubocop/cop/lint/nested_method_definition.rb +1 -1
  106. data/lib/rubocop/cop/lint/non_deterministic_require_order.rb +1 -1
  107. data/lib/rubocop/cop/lint/number_conversion.rb +1 -1
  108. data/lib/rubocop/cop/lint/parentheses_as_grouped_expression.rb +17 -1
  109. data/lib/rubocop/cop/lint/redundant_cop_disable_directive.rb +42 -17
  110. data/lib/rubocop/cop/lint/redundant_dir_glob_sort.rb +13 -2
  111. data/lib/rubocop/cop/lint/unescaped_bracket_in_regexp.rb +18 -1
  112. data/lib/rubocop/cop/lint/useless_assignment.rb +14 -7
  113. data/lib/rubocop/cop/lint/void.rb +8 -1
  114. data/lib/rubocop/cop/lint.rb +1 -0
  115. data/lib/rubocop/cop/metrics/class_length.rb +4 -2
  116. data/lib/rubocop/cop/metrics/cyclomatic_complexity.rb +0 -4
  117. data/lib/rubocop/cop/metrics/module_length.rb +4 -2
  118. data/lib/rubocop/cop/migration/department_name.rb +1 -1
  119. data/lib/rubocop/cop/mixin/alignment.rb +5 -1
  120. data/lib/rubocop/cop/mixin/annotation_comment.rb +1 -1
  121. data/lib/rubocop/cop/mixin/check_line_breakable.rb +17 -14
  122. data/lib/rubocop/cop/mixin/check_single_line_suitability.rb +5 -1
  123. data/lib/rubocop/cop/mixin/code_length.rb +10 -2
  124. data/lib/rubocop/cop/mixin/empty_lines_around_body.rb +12 -1
  125. data/lib/rubocop/cop/mixin/endless_method_body_help.rb +69 -0
  126. data/lib/rubocop/cop/mixin/forbidden_identifiers.rb +2 -2
  127. data/lib/rubocop/cop/mixin/hash_alignment_styles.rb +26 -2
  128. data/lib/rubocop/cop/mixin/hash_shorthand_syntax.rb +31 -3
  129. data/lib/rubocop/cop/mixin/hash_transform_method.rb +1 -1
  130. data/lib/rubocop/cop/mixin/min_branches_count.rb +3 -1
  131. data/lib/rubocop/cop/mixin/multiline_element_line_breaks.rb +11 -1
  132. data/lib/rubocop/cop/mixin/multiline_literal_brace_layout.rb +9 -0
  133. data/lib/rubocop/cop/mixin/ordered_gem_node.rb +1 -1
  134. data/lib/rubocop/cop/mixin/pattern_matching_help.rb +35 -0
  135. data/lib/rubocop/cop/mixin/preceding_following_alignment.rb +29 -3
  136. data/lib/rubocop/cop/mixin/project_index_help.rb +15 -0
  137. data/lib/rubocop/cop/mixin/reparsed_equivalence.rb +13 -3
  138. data/lib/rubocop/cop/mixin/statement_modifier.rb +22 -2
  139. data/lib/rubocop/cop/mixin.rb +4 -2
  140. data/lib/rubocop/cop/naming/accessor_method_name.rb +0 -16
  141. data/lib/rubocop/cop/naming/block_forwarding.rb +4 -1
  142. data/lib/rubocop/cop/naming/block_parameter_name.rb +1 -1
  143. data/lib/rubocop/cop/naming/heredoc_delimiter_case.rb +9 -2
  144. data/lib/rubocop/cop/naming/heredoc_delimiter_naming.rb +5 -3
  145. data/lib/rubocop/cop/naming/predicate_prefix.rb +7 -22
  146. data/lib/rubocop/cop/offense.rb +59 -3
  147. data/lib/rubocop/cop/registry.rb +10 -2
  148. data/lib/rubocop/cop/security/json_load.rb +16 -4
  149. data/lib/rubocop/cop/severity.rb +12 -0
  150. data/lib/rubocop/cop/style/access_modifier_declarations.rb +10 -2
  151. data/lib/rubocop/cop/style/alias.rb +18 -5
  152. data/lib/rubocop/cop/style/ambiguous_endless_method_definition.rb +4 -3
  153. data/lib/rubocop/cop/style/and_or.rb +4 -0
  154. data/lib/rubocop/cop/style/arguments_forwarding.rb +6 -3
  155. data/lib/rubocop/cop/style/attr.rb +3 -4
  156. data/lib/rubocop/cop/style/bitwise_predicate.rb +6 -4
  157. data/lib/rubocop/cop/style/block_delimiters.rb +29 -3
  158. data/lib/rubocop/cop/style/case_like_if.rb +9 -6
  159. data/lib/rubocop/cop/style/class_and_module_children.rb +54 -16
  160. data/lib/rubocop/cop/style/class_methods_definitions.rb +8 -0
  161. data/lib/rubocop/cop/style/combinable_loops.rb +85 -11
  162. data/lib/rubocop/cop/style/concat_array_literals.rb +46 -7
  163. data/lib/rubocop/cop/style/conditional_assignment.rb +4 -0
  164. data/lib/rubocop/cop/style/constant_visibility.rb +6 -2
  165. data/lib/rubocop/cop/style/data_inheritance.rb +14 -0
  166. data/lib/rubocop/cop/style/directive_scope.rb +154 -21
  167. data/lib/rubocop/cop/style/disable_cops_within_source_code_directive.rb +118 -18
  168. data/lib/rubocop/cop/style/documentation_method.rb +1 -9
  169. data/lib/rubocop/cop/style/double_negation.rb +11 -0
  170. data/lib/rubocop/cop/style/each_for_simple_loop.rb +1 -1
  171. data/lib/rubocop/cop/style/empty_block_parameter.rb +1 -1
  172. data/lib/rubocop/cop/style/empty_lambda_parameter.rb +1 -1
  173. data/lib/rubocop/cop/style/empty_literal.rb +1 -1
  174. data/lib/rubocop/cop/style/empty_method.rb +15 -3
  175. data/lib/rubocop/cop/style/endless_method.rb +91 -11
  176. data/lib/rubocop/cop/style/exact_regexp_match.rb +45 -8
  177. data/lib/rubocop/cop/style/explicit_block_argument.rb +50 -10
  178. data/lib/rubocop/cop/style/file_null.rb +2 -2
  179. data/lib/rubocop/cop/style/float_division.rb +28 -14
  180. data/lib/rubocop/cop/style/for.rb +44 -1
  181. data/lib/rubocop/cop/style/format_string.rb +28 -6
  182. data/lib/rubocop/cop/style/format_string_token.rb +1 -1
  183. data/lib/rubocop/cop/style/frozen_string_literal_comment.rb +2 -1
  184. data/lib/rubocop/cop/style/guard_clause.rb +13 -1
  185. data/lib/rubocop/cop/style/hash_conversion.rb +28 -8
  186. data/lib/rubocop/cop/style/hash_lookup_method.rb +35 -9
  187. data/lib/rubocop/cop/style/hash_syntax.rb +16 -3
  188. data/lib/rubocop/cop/style/identical_conditional_branches.rb +22 -3
  189. data/lib/rubocop/cop/style/if_inside_else.rb +21 -1
  190. data/lib/rubocop/cop/style/if_unless_modifier.rb +19 -20
  191. data/lib/rubocop/cop/style/if_unless_modifier_of_if_unless.rb +4 -0
  192. data/lib/rubocop/cop/style/if_with_semicolon.rb +47 -4
  193. data/lib/rubocop/cop/style/inverse_methods.rb +24 -2
  194. data/lib/rubocop/cop/style/it_block_parameter.rb +32 -3
  195. data/lib/rubocop/cop/style/keyword_parameters_order.rb +3 -1
  196. data/lib/rubocop/cop/style/lambda.rb +18 -0
  197. data/lib/rubocop/cop/style/lambda_call.rb +7 -2
  198. data/lib/rubocop/cop/style/magic_comment_format.rb +1 -1
  199. data/lib/rubocop/cop/style/map_into_array.rb +1 -2
  200. data/lib/rubocop/cop/style/method_call_with_args_parentheses/omit_parentheses.rb +45 -10
  201. data/lib/rubocop/cop/style/method_call_with_args_parentheses.rb +25 -2
  202. data/lib/rubocop/cop/style/method_call_without_args_parentheses.rb +26 -3
  203. data/lib/rubocop/cop/style/method_def_parentheses.rb +29 -3
  204. data/lib/rubocop/cop/style/missing_else.rb +7 -1
  205. data/lib/rubocop/cop/style/multiline_if_modifier.rb +45 -5
  206. data/lib/rubocop/cop/style/multiline_in_pattern_then.rb +7 -0
  207. data/lib/rubocop/cop/style/multiline_method_signature.rb +2 -5
  208. data/lib/rubocop/cop/style/multiline_ternary_operator.rb +7 -3
  209. data/lib/rubocop/cop/style/multiline_when_then.rb +7 -0
  210. data/lib/rubocop/cop/style/mutable_constant.rb +28 -1
  211. data/lib/rubocop/cop/style/negated_if_else_condition.rb +2 -1
  212. data/lib/rubocop/cop/style/negative_array_index.rb +9 -1
  213. data/lib/rubocop/cop/style/nested_modifier.rb +36 -12
  214. data/lib/rubocop/cop/style/nested_ternary_operator.rb +26 -8
  215. data/lib/rubocop/cop/style/next.rb +24 -10
  216. data/lib/rubocop/cop/style/nil_comparison.rb +48 -8
  217. data/lib/rubocop/cop/style/nil_lambda.rb +1 -1
  218. data/lib/rubocop/cop/style/non_nil_check.rb +18 -2
  219. data/lib/rubocop/cop/style/not.rb +10 -3
  220. data/lib/rubocop/cop/style/numeric_literal_prefix.rb +11 -1
  221. data/lib/rubocop/cop/style/numeric_predicate.rb +21 -5
  222. data/lib/rubocop/cop/style/one_line_conditional.rb +14 -3
  223. data/lib/rubocop/cop/style/parallel_assignment.rb +1 -1
  224. data/lib/rubocop/cop/style/parentheses_around_condition.rb +6 -1
  225. data/lib/rubocop/cop/style/percent_literal_delimiters.rb +14 -3
  226. data/lib/rubocop/cop/style/reduce_to_hash.rb +17 -1
  227. data/lib/rubocop/cop/style/redundant_begin.rb +20 -3
  228. data/lib/rubocop/cop/style/redundant_condition.rb +34 -17
  229. data/lib/rubocop/cop/style/redundant_double_splat_hash_braces.rb +16 -1
  230. data/lib/rubocop/cop/style/redundant_exception.rb +15 -3
  231. data/lib/rubocop/cop/style/redundant_fetch_block.rb +10 -2
  232. data/lib/rubocop/cop/style/redundant_freeze.rb +10 -1
  233. data/lib/rubocop/cop/style/redundant_parentheses.rb +23 -2
  234. data/lib/rubocop/cop/style/redundant_regexp_argument.rb +7 -0
  235. data/lib/rubocop/cop/style/redundant_regexp_character_class.rb +6 -5
  236. data/lib/rubocop/cop/style/redundant_regexp_constructor.rb +25 -5
  237. data/lib/rubocop/cop/style/redundant_regexp_escape.rb +4 -0
  238. data/lib/rubocop/cop/style/regexp_literal.rb +75 -26
  239. data/lib/rubocop/cop/style/require_order.rb +9 -0
  240. data/lib/rubocop/cop/style/rescue_modifier.rb +5 -9
  241. data/lib/rubocop/cop/style/return_nil.rb +17 -2
  242. data/lib/rubocop/cop/style/return_nil_in_predicate_method_definition.rb +11 -1
  243. data/lib/rubocop/cop/style/sample.rb +9 -8
  244. data/lib/rubocop/cop/style/self_assignment.rb +12 -15
  245. data/lib/rubocop/cop/style/semicolon.rb +16 -5
  246. data/lib/rubocop/cop/style/single_line_block_params.rb +1 -1
  247. data/lib/rubocop/cop/style/single_line_methods.rb +16 -4
  248. data/lib/rubocop/cop/style/special_global_vars.rb +1 -1
  249. data/lib/rubocop/cop/style/stabby_lambda_parentheses.rb +29 -5
  250. data/lib/rubocop/cop/style/string_concatenation.rb +9 -3
  251. data/lib/rubocop/cop/style/struct_inheritance.rb +14 -0
  252. data/lib/rubocop/cop/style/super_arguments.rb +3 -0
  253. data/lib/rubocop/cop/style/symbol_proc.rb +23 -5
  254. data/lib/rubocop/cop/style/ternary_parentheses.rb +23 -1
  255. data/lib/rubocop/cop/style/trailing_comma_in_block_args.rb +1 -1
  256. data/lib/rubocop/cop/style/trailing_underscore_variable.rb +23 -19
  257. data/lib/rubocop/cop/style/while_until_modifier.rb +1 -1
  258. data/lib/rubocop/cop/style/yoda_condition.rb +24 -9
  259. data/lib/rubocop/cop/style/yoda_expression.rb +10 -1
  260. data/lib/rubocop/cop/style.rb +0 -1
  261. data/lib/rubocop/cop/team.rb +10 -5
  262. data/lib/rubocop/cop/util.rb +1 -1
  263. data/lib/rubocop/cop/variable_force/assignment.rb +1 -1
  264. data/lib/rubocop/cop/variable_force/variable.rb +14 -1
  265. data/lib/rubocop/cops_documentation_generator.rb +23 -2
  266. data/lib/rubocop/directive_comment.rb +37 -8
  267. data/lib/rubocop/formatter/base_formatter.rb +22 -0
  268. data/lib/rubocop/formatter/disabled_config_formatter.rb +19 -1
  269. data/lib/rubocop/formatter/formatter_set.rb +6 -1
  270. data/lib/rubocop/formatter/github_actions_formatter.rb +6 -5
  271. data/lib/rubocop/formatter/html_formatter.rb +2 -2
  272. data/lib/rubocop/formatter/json_formatter.rb +71 -1
  273. data/lib/rubocop/formatter/junit_formatter.rb +1 -1
  274. data/lib/rubocop/formatter/offense_count_formatter.rb +3 -1
  275. data/lib/rubocop/formatter/pacman_formatter.rb +3 -1
  276. data/lib/rubocop/formatter/sarif_formatter.rb +186 -0
  277. data/lib/rubocop/formatter/worst_offenders_formatter.rb +3 -1
  278. data/lib/rubocop/formatter.rb +1 -0
  279. data/lib/rubocop/lsp/runtime.rb +47 -11
  280. data/lib/rubocop/lsp/stdin_runner.rb +11 -5
  281. data/lib/rubocop/lsp.rb +5 -2
  282. data/lib/rubocop/magic_comment.rb +27 -0
  283. data/lib/rubocop/mcp/autocorrection_request.rb +52 -0
  284. data/lib/rubocop/mcp/explain_tool.rb +68 -0
  285. data/lib/rubocop/mcp/scope.rb +65 -0
  286. data/lib/rubocop/mcp/server.rb +184 -76
  287. data/lib/rubocop/mcp/source_files.rb +35 -0
  288. data/lib/rubocop/offense_limit.rb +30 -0
  289. data/lib/rubocop/options.rb +97 -4
  290. data/lib/rubocop/path_util.rb +1 -1
  291. data/lib/rubocop/plugin/configuration_integrator.rb +12 -5
  292. data/lib/rubocop/plugin/loader.rb +1 -1
  293. data/lib/rubocop/rake_task.rb +1 -1
  294. data/lib/rubocop/remote_config.rb +8 -5
  295. data/lib/rubocop/result_cache.rb +36 -18
  296. data/lib/rubocop/runner.rb +160 -49
  297. data/lib/rubocop/server/cache.rb +2 -2
  298. data/lib/rubocop/unified_diff.rb +257 -0
  299. data/lib/rubocop/util.rb +69 -0
  300. data/lib/rubocop/version.rb +1 -1
  301. data/lib/rubocop.rb +9 -1
  302. metadata +21 -6
  303. data/lib/rubocop/cop/style/double_cop_disable_directive.rb +0 -46
@@ -80,6 +80,11 @@ removed:
80
80
  `Lint/BinaryOperatorWithIdenticalOperands`. Please use
81
81
  `Lint/BinaryOperatorWithIdenticalOperands` instead
82
82
  Style/BracesAroundHashParameters: true
83
+ Style/DoubleCopDisableDirective:
84
+ severity: warning
85
+ reason: >
86
+ it has been superseded by `Lint/CopDirectiveSyntax`. Please use
87
+ `Lint/CopDirectiveSyntax` instead
83
88
  Style/MethodMissingSuper:
84
89
  reason: >
85
90
  it has been superseded by `Lint/MissingSuper`. Please use
@@ -149,6 +154,10 @@ changed_parameters:
149
154
  parameters: IgnoreCopDirectives
150
155
  alternative: AllowCopDirectives
151
156
  severity: warning
157
+ - cops: Style/DisableCopsWithinSourceCodeDirective
158
+ parameters: AllowTrailingComment
159
+ alternative: AllowWithReason
160
+ severity: warning
152
161
  - cops:
153
162
  - Lint/BlockAlignment
154
163
  - Layout/BlockAlignment
@@ -35,16 +35,44 @@ module RuboCop
35
35
  cop_name: offense.cop_name,
36
36
  status: status || offense.status
37
37
  }
38
- # Only offenses suppressed by a directive can carry one, so keep the key out of the
39
- # common case rather than writing a null for every cached offense.
40
- hash[:justification] = offense.justification if offense.justification
38
+ add_justification(hash, offense)
39
+ add_corrections(hash, offense)
41
40
  hash
42
41
  end
43
42
 
44
43
  def message(offense)
45
- # JSON.dump will fail if the offense message contains text which is not
46
- # valid UTF-8
47
- offense.message.dup.force_encoding(::Encoding::UTF_8).scrub
44
+ encode_to_utf8(offense.message)
45
+ end
46
+
47
+ def encode_to_utf8(value)
48
+ string = value.to_s
49
+
50
+ if string.encoding == ::Encoding::BINARY
51
+ string.dup.force_encoding(::Encoding::UTF_8).scrub
52
+ else
53
+ string.encode(::Encoding::UTF_8, invalid: :replace, undef: :replace)
54
+ end
55
+ end
56
+
57
+ # Only offenses suppressed by a directive can carry one, so keep the key out of the
58
+ # common case rather than writing a null for every cached offense.
59
+ def add_justification(hash, offense)
60
+ return unless offense.justification
61
+
62
+ hash[:justification] = encode_to_utf8(offense.justification)
63
+ end
64
+
65
+ # A corrector cannot be serialized, so the edits it would make are cached
66
+ # separately; without this a cache hit would report no corrections.
67
+ def add_corrections(hash, offense)
68
+ return if offense.corrections.empty?
69
+
70
+ hash[:corrections] = offense.corrections.map do |correction|
71
+ { begin_pos: correction.begin_pos,
72
+ end_pos: correction.end_pos,
73
+ replacement: encode_to_utf8(correction.replacement) }
74
+ end
75
+ hash[:correction_safe] = offense.correction_safe
48
76
  end
49
77
 
50
78
  # Restore an offense object loaded from a JSON file.
@@ -52,7 +80,19 @@ module RuboCop
52
80
  offenses.map! do |o|
53
81
  location = location_from_source_buffer(o)
54
82
  Cop::Offense.new(o['severity'], location, o['message'], o['cop_name'], o['status'].to_sym,
55
- justification: o['justification'])
83
+ justification: o['justification'],
84
+ corrections: corrections_from_cache(o),
85
+ correction_safe: o.fetch('correction_safe', true))
86
+ end
87
+ end
88
+
89
+ def corrections_from_cache(offense)
90
+ cached = offense['corrections']
91
+ return [] unless cached
92
+
93
+ cached.map do |correction|
94
+ Cop::Offense::Correction.new(correction['begin_pos'], correction['end_pos'],
95
+ correction['replacement']).freeze
56
96
  end
57
97
  end
58
98
 
@@ -0,0 +1,134 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'open3'
4
+
5
+ module RuboCop
6
+ # Lists the files that differ from a git revision, for `--changed`.
7
+ #
8
+ # Shelling out to `git` keeps RuboCop free of a version control dependency,
9
+ # and means the answer is exactly what git would give on the command line.
10
+ #
11
+ # @api private
12
+ class ChangedFiles
13
+ DEFAULT_REVISION = 'HEAD'
14
+ FILESYSTEM_ENCODING = Encoding.find('filesystem')
15
+
16
+ # Whether git can resolve `name` to a commit in the current repository.
17
+ def self.revision?(name)
18
+ _stdout, _stderr, status =
19
+ Open3.capture3('git', 'rev-parse', '--verify', '--quiet', "#{name}^{commit}")
20
+ status.success?
21
+ rescue Errno::ENOENT
22
+ false
23
+ end
24
+
25
+ def initialize(revision = nil)
26
+ @revision = revision || DEFAULT_REVISION
27
+ end
28
+
29
+ # Files that were added or modified since the revision, as absolute paths.
30
+ # Deleted files are left out - there is nothing left to inspect - and
31
+ # untracked files are included, since a file that is new to the working
32
+ # tree has changed as surely as one git already knows about.
33
+ #
34
+ # @return [Set<String>]
35
+ def paths
36
+ root = repository_root
37
+
38
+ (modified_paths + untracked_paths).to_set { |path| File.expand_path(path, root) }
39
+ end
40
+
41
+ # @return [Array<String>] the files among these that changed
42
+ def filter(files)
43
+ changed = paths
44
+ files.select { |file| changed.include?(file) }
45
+ end
46
+
47
+ private
48
+
49
+ def repository_root
50
+ filesystem_path(git('rev-parse', '--show-toplevel').b.chomp)
51
+ end
52
+
53
+ def modified_paths(revision = @revision)
54
+ # `diff.relative` would make git report paths relative to the current
55
+ # directory rather than to the repository root.
56
+ split_paths(
57
+ git('-c', 'diff.relative=false', 'diff', '--name-only', '--diff-filter=d', '-z', revision)
58
+ )
59
+ rescue Error
60
+ raise unless revision == DEFAULT_REVISION && unborn_head?
61
+
62
+ # A repository without commits has no `HEAD` to diff against, but files
63
+ # can already be staged in it. Against the empty tree every one of them
64
+ # counts as changed, which is what the caller wants to hear rather than
65
+ # an error about `HEAD` not resolving.
66
+ modified_paths(empty_tree)
67
+ end
68
+
69
+ def unborn_head?
70
+ !self.class.revision?(DEFAULT_REVISION)
71
+ end
72
+
73
+ # Hashed rather than hardcoded, since its id depends on the repository's
74
+ # object format (SHA-1 or SHA-256).
75
+ def empty_tree
76
+ git('hash-object', '-t', 'tree', '--stdin').chomp
77
+ end
78
+
79
+ def untracked_paths
80
+ # Unlike `git diff`, `ls-files` only looks under the current directory, so
81
+ # the `:/` pathspec widens it to the whole repository, and `--full-name`
82
+ # anchors the paths to the repository root the way `git diff` does.
83
+ split_paths(
84
+ git('ls-files', '--others', '--exclude-standard', '--full-name', '-z', '--', ':/')
85
+ )
86
+ end
87
+
88
+ # The `-z` above is what makes these usable: without it git wraps any path
89
+ # that is not plain ASCII in quotes and octal escapes, and those files drop
90
+ # out of the comparison without a word.
91
+ def split_paths(output)
92
+ output.b.split("\0").reject(&:empty?).map { |path| filesystem_path(path) }
93
+ end
94
+
95
+ # git hands back bytes, which Ruby tags with the encoding the process
96
+ # happens to have been started in - splitting those bytes as US-ASCII raises
97
+ # on any path that is not. They have to end up in the encoding `Dir.glob`
98
+ # uses, or a path with a non-ASCII character in it never matches the file
99
+ # list it is compared against.
100
+ #
101
+ # On POSIX those bytes are already the filesystem's own, so the encoding is
102
+ # simply corrected. Windows is the exception: git converts paths to UTF-8 on
103
+ # the way out, while Ruby reports file names in the local code page, so the
104
+ # two have to be reconciled.
105
+ def filesystem_path(path)
106
+ path = path.dup
107
+
108
+ return path.force_encoding(FILESYSTEM_ENCODING) unless Platform.windows?
109
+
110
+ path.force_encoding(Encoding::UTF_8).encode(FILESYSTEM_ENCODING)
111
+ rescue Encoding::UndefinedConversionError, Encoding::InvalidByteSequenceError
112
+ # A name the local code page cannot represent. Nothing good is left to do
113
+ # with it, and dropping it would be worse than reporting it as-is.
114
+ path.force_encoding(FILESYSTEM_ENCODING)
115
+ end
116
+
117
+ def git(*args)
118
+ stdout, stderr, status = Open3.capture3('git', *args)
119
+
120
+ raise Error, git_error_message(stderr) unless status.success?
121
+
122
+ stdout
123
+ rescue Errno::ENOENT
124
+ raise Error, '--changed requires git to be installed and on your PATH.'
125
+ end
126
+
127
+ def git_error_message(stderr)
128
+ message = stderr.strip
129
+ message = 'git failed with no output on standard error.' if message.empty?
130
+
131
+ "--changed could not ask git which files changed: #{message}"
132
+ end
133
+ end
134
+ end
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ class CLI
5
+ module Command
6
+ # Shared handling for commands that take a list of cop names.
7
+ #
8
+ # Validation happens here rather than during option parsing because the
9
+ # registry is not complete until the configuration has been loaded, which
10
+ # is also why `--only` and `--except` are validated from the runner.
11
+ # @api private
12
+ module CopNames
13
+ private
14
+
15
+ # The cops among `names` that exist, so a command can report on what it
16
+ # understood before {#validate_cop_names!} objects to the rest.
17
+ def known_cop_classes(names)
18
+ names.filter_map { |name| cop_class_for(name) }
19
+ end
20
+
21
+ # @raise [IncorrectCopNameError] if any name is not a cop
22
+ def validate_cop_names!(names)
23
+ unknown = names.reject { |name| cop_class_for(name) }
24
+ return if unknown.empty?
25
+
26
+ raise IncorrectCopNameError, unknown.map { |name| unknown_cop_message(name) }.join("\n")
27
+ end
28
+
29
+ # Not memoized: the MCP server lives long enough for a plugin to register
30
+ # a cop after a lookup has missed it.
31
+ def cop_class_for(name)
32
+ Cop::Registry.global.find_by_cop_name(name)
33
+ end
34
+
35
+ def unknown_cop_message(name)
36
+ return department_message(name) if department?(name)
37
+
38
+ message = "Unrecognized cop: #{name}."
39
+ similar = NameSimilarity.find_similar_names(name, Cop::Registry.global.names)
40
+ return message if similar.empty?
41
+
42
+ "#{message}\nDid you mean? #{similar.join(', ')}"
43
+ end
44
+
45
+ def department?(name)
46
+ Cop::Registry.global.departments.any? { |department| department.to_s == name }
47
+ end
48
+
49
+ def department_message(name)
50
+ example = Cop::Registry.global.names.find { |cop| cop.start_with?("#{name}/") }
51
+
52
+ "#{name} is a department, not a cop. Name one of its cops, such as #{example}."
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
@@ -9,7 +9,7 @@ module RuboCop
9
9
  include Formatter::TextUtil
10
10
 
11
11
  # Combination of short and long formatter names.
12
- INTEGRATION_FORMATTERS = %w[h html j json ju junit].freeze
12
+ INTEGRATION_FORMATTERS = %w[h html j json ju junit sa sarif].freeze
13
13
 
14
14
  self.command_name = :execute_runner
15
15
 
@@ -28,7 +28,16 @@ module RuboCop
28
28
  all_passed || @options[:auto_gen_config]
29
29
  end
30
30
 
31
- maybe_print_corrected_source
31
+ if @options[:diff]
32
+ print_diffs(runner.diffs)
33
+ else
34
+ maybe_print_corrected_source
35
+ end
36
+
37
+ # A diff means autocorrectable offenses are still in the working
38
+ # tree, so the run has not passed even though they were "corrected"
39
+ # in memory.
40
+ all_pass_or_excluded &&= runner.diffs.empty?
32
41
 
33
42
  merge_todo_audit_status(runner_status(runner, all_pass_or_excluded))
34
43
  end
@@ -119,9 +128,20 @@ module RuboCop
119
128
  "#{Gem.loaded_specs['rubocop'].metadata['bug_tracker_uri']}\n"
120
129
  end
121
130
 
131
+ def print_diffs(diffs)
132
+ return if diffs.empty?
133
+ # Integration tools own stdout when they ask for a machine-readable
134
+ # format, so the diff would only corrupt their input.
135
+ return if INTEGRATION_FORMATTERS.include?(@options[:format])
136
+
137
+ output = @options[:stderr] ? $stderr : $stdout
138
+ output.puts
139
+ diffs.each { |diff| output.print(diff) }
140
+ end
141
+
122
142
  def maybe_print_corrected_source
123
143
  # Integration tools (like RubyMine) expect to have only the JSON result
124
- # when specifying JSON format. Similar HTML and JUnit are targeted as well.
144
+ # when specifying JSON format. Similar HTML, JUnit and SARIF are targeted as well.
125
145
  # See: https://github.com/rubocop/rubocop/issues/8673
126
146
  return if INTEGRATION_FORMATTERS.include?(@options[:format])
127
147
 
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ class CLI
5
+ module Command
6
+ # Explains what cops do, as described by {Cop::CopExplanation}.
7
+ # @api private
8
+ class Explain < Base
9
+ include CopNames
10
+
11
+ self.command_name = :explain
12
+
13
+ def initialize(env)
14
+ super
15
+
16
+ @config = @config_store.for(PathUtil.pwd)
17
+ end
18
+
19
+ def run
20
+ names = @options[:explain]
21
+
22
+ known_cop_classes(names).each_with_index do |cop_class, index|
23
+ puts if index.positive?
24
+ puts Cop::CopExplanation.new(cop_class, @config)
25
+ end
26
+
27
+ # Checked after printing, so a typo in a list does not cost you the
28
+ # explanations you asked for alongside it.
29
+ validate_cop_names!(names)
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -7,6 +7,8 @@ module RuboCop
7
7
  # for the current directory.
8
8
  # @api private
9
9
  class ShowCops < Base
10
+ include CopNames
11
+
10
12
  self.command_name = :show_cops
11
13
 
12
14
  ExactMatcher = Struct.new(:pattern) do
@@ -38,6 +40,10 @@ module RuboCop
38
40
 
39
41
  def run
40
42
  print_available_cops
43
+ # Checked after printing, so a typo alongside good names still shows
44
+ # what was asked for. Wildcards are patterns, not names, so a pattern
45
+ # that matches nothing is not an error.
46
+ validate_cop_names!(@options[:show_cops].grep_v(/\*/))
41
47
  end
42
48
 
43
49
  private
@@ -7,6 +7,8 @@ module RuboCop
7
7
  # or documentation base url by default.
8
8
  # @api private
9
9
  class ShowDocsUrl < Base
10
+ include CopNames
11
+
10
12
  self.command_name = :show_docs_url
11
13
 
12
14
  def initialize(env)
@@ -17,6 +19,9 @@ module RuboCop
17
19
 
18
20
  def run
19
21
  print_documentation_url
22
+ # Checked after printing, so a typo alongside good names still gives
23
+ # you the urls you asked for.
24
+ validate_cop_names!(cops_array)
20
25
  end
21
26
 
22
27
  private
@@ -24,10 +29,7 @@ module RuboCop
24
29
  def print_documentation_url
25
30
  puts Cop::Documentation.default_base_url if cops_array.empty?
26
31
 
27
- cops_array.each do |cop_name|
28
- cop = Cop::Registry.global.find_by_cop_name(cop_name)
29
- next unless cop
30
-
32
+ known_cop_classes(cops_array).each do |cop|
31
33
  url = Cop::Documentation.url_for(cop, @config)
32
34
  puts url if url
33
35
  end
data/lib/rubocop/cli.rb CHANGED
@@ -13,7 +13,8 @@ module RuboCop
13
13
  DEFAULT_PARALLEL_OPTIONS = %i[
14
14
  color config debug display_style_guide display_time display_only_fail_level_offenses
15
15
  display_only_failed editor_mode except extra_details fail_level fix_layout format formatters
16
- ignore_disable_comments lint only only_guide_cops out require safe
16
+ ignore_disable_comments lint max_offenses_per_cop only only_guide_cops out preview require
17
+ safe
17
18
  autocorrect safe_autocorrect autocorrect_all
18
19
  ].freeze
19
20
 
@@ -183,12 +184,18 @@ module RuboCop
183
184
 
184
185
  def set_options_to_config_loader
185
186
  ConfigLoader.debug = @options[:debug]
186
- ConfigLoader.disable_pending_cops = @options[:disable_pending_cops]
187
- ConfigLoader.enable_pending_cops = @options[:enable_pending_cops]
188
187
  ConfigLoader.ignore_parent_exclusion = @options[:ignore_parent_exclusion]
189
188
  ConfigLoader.ignore_unrecognized_cops = @options[:ignore_unrecognized_cops]
189
+ set_cop_selection_options_to_config_loader
190
+ end
191
+
192
+ # Options that decide which cops run, as opposed to how configuration is loaded.
193
+ def set_cop_selection_options_to_config_loader
194
+ ConfigLoader.disable_pending_cops = @options[:disable_pending_cops]
195
+ ConfigLoader.enable_pending_cops = @options[:enable_pending_cops]
190
196
  ConfigLoader.enabled_by_default = @options[:enable_all_cops]
191
197
  ConfigLoader.disabled_by_default = @options[:disable_all_cops]
198
+ ConfigLoader.preview = @options[:preview]
192
199
  end
193
200
 
194
201
  def set_options_to_pending_cops_reporter
@@ -204,7 +211,7 @@ module RuboCop
204
211
  return unless Options::EXITING_OPTIONS.any? { |o| @options.key? o }
205
212
 
206
213
  run_command(:version) if @options[:version] || @options[:verbose_version]
207
- %i[show_cops list_enabled_cops_for show_docs_url lsp mcp].each do |name|
214
+ %i[show_cops list_enabled_cops_for show_docs_url explain lsp mcp].each do |name|
208
215
  run_command(name) if @options[name]
209
216
  end
210
217
  raise Finished
@@ -22,8 +22,10 @@ module RuboCop
22
22
  CopAnalysis = Struct.new(:line_ranges, :start_line_number, :start_directive) do
23
23
  # The open range (if any) closed at the given line, appended to the
24
24
  # accumulated ranges, each remembering the directive that opened it.
25
+ # Closing above the opening line (e.g. a `pop` directly below a
26
+ # statement whose disable a `next +Cop` suspended) yields no range.
25
27
  def close(line)
26
- return line_ranges unless start_line_number
28
+ return line_ranges if !start_line_number || line < start_line_number
27
29
 
28
30
  line_ranges + [DirectiveRange.new(start_line_number, line, start_directive)]
29
31
  end
@@ -2,14 +2,15 @@
2
2
 
3
3
  module RuboCop
4
4
  class CommentConfig
5
- # Handling of `disable-next` directives: computing the statement scope
6
- # they apply to and tracking directives that attached to nothing.
5
+ # Handling of next-statement directives (`disable-next`, `todo-next` and
6
+ # `next`): computing the statement scope they apply to and tracking
7
+ # directives that attached to nothing.
7
8
  # The scope is the whole statement starting on the next line bearing
8
9
  # code. Comment-only lines between the directive and the code chain (so
9
10
  # several directives can stack), while a blank line breaks the attachment.
10
11
  # @api private
11
12
  module DisableNext
12
- # `disable-next` directives that suppress nothing: nothing follows
13
+ # Next-statement directives that affect nothing: nothing follows
13
14
  # them, or they sit at the end of a code line.
14
15
  def detached_next_directives
15
16
  cop_disabled_line_ranges # ensure the analysis that collects them ran
@@ -48,6 +49,51 @@ module RuboCop
48
49
  analysis.start_line_number, analysis.start_directive)
49
50
  end
50
51
 
52
+ # Applies a `next` directive: `-` cops are disabled for the attached
53
+ # statement (exactly like `disable-next`), `+` cops have any open
54
+ # disable suspended for it. The `resolved_cops` come pre-expanded and
55
+ # qualified, mirroring `push`.
56
+ def apply_next_directive(analyses, resolved_cops, directive)
57
+ bounds = statement_scope_after(directive.line_number)
58
+ return @detached_next_directives << directive unless bounds
59
+
60
+ range = DirectiveRange.new(bounds.begin, bounds.end, directive)
61
+ resolved_cops.each do |op, cops|
62
+ cops.each do |cop|
63
+ if op == '-'
64
+ add_next_range(analyses, cop, range)
65
+ else
66
+ suspend_disable(analyses, cop, bounds)
67
+ end
68
+ end
69
+ end
70
+ end
71
+
72
+ # Applies an `enable-next` directive: every listed cop (departments
73
+ # and `all` included) has any open disable suspended for the attached
74
+ # statement.
75
+ def apply_enable_next(analyses, directive)
76
+ bounds = statement_scope_after(directive.line_number)
77
+ return @detached_next_directives << directive unless bounds
78
+
79
+ directive.cop_names.each do |cop_name|
80
+ suspend_disable(analyses, qualified_cop_name(cop_name), bounds)
81
+ end
82
+ end
83
+
84
+ # Punches a statement-sized hole into the currently open disable of the
85
+ # cop (whether opened by a directive or injected for a config-disabled
86
+ # cop): the open range closes just above the statement and reopens
87
+ # right below it, still attributed to its opening directive. Aligned
88
+ # with `push`, a `+` for a cop that is not disabled is a no-op.
89
+ def suspend_disable(analyses, cop, bounds)
90
+ analysis = analyses[cop]
91
+ return unless analysis.start_line_number
92
+
93
+ analyses[cop] = CopAnalysis.new(analysis.close(bounds.begin - 1),
94
+ bounds.end + 1, analysis.start_directive)
95
+ end
96
+
51
97
  def attached_code_line(directive_line)
52
98
  ((directive_line + 1)..processed_source.lines.size).each do |line|
53
99
  return line unless comment_only_line?(line)
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ class CommentConfig
5
+ # Handling of `push`/`pop` directives and their signed cop arguments:
6
+ # saving and restoring the per-cop analysis state, and resolving the
7
+ # `+`/`-` argument lists (shared with the `next` directive).
8
+ # @api private
9
+ module PushPop
10
+ private
11
+
12
+ def resolve_push_cops(directive)
13
+ directive.push_args.transform_values do |names|
14
+ names.flat_map { |name| expand_cop_name(name) }
15
+ end
16
+ end
17
+
18
+ def expand_cop_name(name)
19
+ registry = Cop::Registry.global
20
+ cops = registry.department?(name) ? registry.names_for_department(name) : [name]
21
+ cops.map { |c| qualified_cop_name(c) }
22
+ end
23
+
24
+ def apply_push(analyses, resolved_cops, directive)
25
+ resolved_cops.each do |op, cops|
26
+ cops.each { |cop| apply_cop_op(analyses, op, cop, directive) }
27
+ end
28
+ end
29
+
30
+ def apply_cop_op(analyses, operation, cop, directive)
31
+ analysis = analyses[cop]
32
+ line = directive.line_number
33
+ if operation == '-' && !analysis.start_line_number
34
+ analyses[cop] = CopAnalysis.new(analysis.line_ranges, line, directive)
35
+ elsif operation == '+' && analysis.start_line_number
36
+ analyses[cop] = CopAnalysis.new(analysis.close(line), nil)
37
+ end
38
+ end
39
+
40
+ def pop_state(analyses, line)
41
+ restore_point = @stack.pop
42
+ (restore_point.keys | analyses.keys).each do |cop|
43
+ analyses[cop] = popped_analysis(analyses[cop], restore_point[cop], line)
44
+ end
45
+ end
46
+
47
+ def popped_analysis(current, restored, line)
48
+ ranges = current.close(line - 1)
49
+ return CopAnalysis.new(ranges, nil) unless restored&.start_line_number
50
+
51
+ CopAnalysis.new(ranges, line, restored.start_directive)
52
+ end
53
+ end
54
+ end
55
+ end