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
@@ -131,7 +131,9 @@ module RuboCop
131
131
  #
132
132
  class EndlessMethod < Base
133
133
  include ConfigurableEnforcedStyle
134
+ include LineLengthHelp
134
135
  include EndlessMethodRewriter
136
+ include EndlessMethodBodyHelp
135
137
  extend TargetRubyVersion
136
138
  extend AutoCorrector
137
139
 
@@ -143,6 +145,12 @@ module RuboCop
143
145
  MSG_REQUIRE_SINGLE = 'Use endless method definitions for single line methods.'
144
146
  MSG_REQUIRE_ALWAYS = 'Use endless method definitions.'
145
147
 
148
+ # `Style/MethodDefParentheses` adds the same parentheses a parameter list needs in
149
+ # an endless definition, so letting both correct in the same pass would double them.
150
+ def self.autocorrect_incompatible_with
151
+ [Style::MethodCallWithArgsParentheses, Style::MethodDefParentheses]
152
+ end
153
+
146
154
  def on_def(node)
147
155
  return if node.assignment_method? || use_heredoc?(node)
148
156
 
@@ -166,6 +174,8 @@ module RuboCop
166
174
  return if node.single_line? || style == :allow_always
167
175
 
168
176
  add_offense(node, message: MSG_MULTI_LINE) do |corrector|
177
+ next if endless_parent?(node)
178
+
169
179
  correct_to_multiline(corrector, node)
170
180
  end
171
181
  end
@@ -175,11 +185,11 @@ module RuboCop
175
185
  add_offense(node, message: MSG_MULTI_LINE) do |corrector|
176
186
  correct_to_multiline(corrector, node)
177
187
  end
178
- elsif !node.endless? && can_be_made_endless?(node) && node.body.single_line?
188
+ elsif !node.endless? && can_be_made_endless?(node) && single_line_when_made_endless?(node)
179
189
  return if too_long_when_made_endless?(node)
180
190
 
181
191
  add_offense(node, message: MSG_REQUIRE_SINGLE) do |corrector|
182
- corrector.replace(node, endless_replacement(node))
192
+ correct_to_endless(corrector, node)
183
193
  end
184
194
  end
185
195
  end
@@ -189,21 +199,58 @@ module RuboCop
189
199
  return if too_long_when_made_endless?(node)
190
200
 
191
201
  add_offense(node, message: MSG_REQUIRE_ALWAYS) do |corrector|
192
- corrector.replace(node, endless_replacement(node))
202
+ correct_to_endless(corrector, node)
193
203
  end
194
204
  end
195
205
 
196
206
  def handle_disallow_style(node)
197
207
  return unless node.endless?
198
208
 
199
- add_offense(node) { |corrector| correct_to_multiline(corrector, node) }
209
+ add_offense(node) do |corrector|
210
+ next if endless_parent?(node)
211
+
212
+ correct_to_multiline(corrector, node)
213
+ end
214
+ end
215
+
216
+ def endless_parent?(node)
217
+ node.parent&.any_def_type? && node.parent.endless?
200
218
  end
201
219
 
202
220
  def use_heredoc?(node)
203
221
  return false unless (body = node.body)
204
222
  return true if body.any_str_type? && body.heredoc?
205
223
 
206
- body.each_descendant(:str).any?(&:heredoc?)
224
+ body.each_descendant(:any_str).any?(&:heredoc?)
225
+ end
226
+
227
+ def correct_to_endless(corrector, node)
228
+ if unparenthesized_arguments?(node)
229
+ corrector.replace(name_to_arguments_range(node), '(')
230
+ corrector.replace(signature_to_body_range(node), ') = ')
231
+ else
232
+ corrector.replace(signature_to_body_range(node), ' = ')
233
+ end
234
+
235
+ corrector.remove(body_to_end_range(node))
236
+ end
237
+
238
+ def unparenthesized_arguments?(node)
239
+ node.arguments.any? && !node.arguments.loc.begin
240
+ end
241
+
242
+ def name_to_arguments_range(node)
243
+ node.loc.name.end.join(node.arguments.source_range.begin)
244
+ end
245
+
246
+ def signature_to_body_range(node)
247
+ signature_end = node.arguments.any? ? node.arguments.source_range.end : node.loc.name.end
248
+
249
+ signature_end.join(node.body.source_range.begin)
250
+ end
251
+
252
+ def body_to_end_range(node)
253
+ node.body.source_range.end.join(node.loc.end.end)
207
254
  end
208
255
 
209
256
  def endless_replacement(node)
@@ -217,23 +264,56 @@ module RuboCop
217
264
  end
218
265
 
219
266
  def arguments(node, missing = '')
220
- node.arguments.any? ? node.arguments.source : missing
267
+ return missing unless node.arguments.any?
268
+ return parenthesized_arguments(node) if unparenthesized_arguments?(node)
269
+
270
+ node.arguments.source
271
+ end
272
+
273
+ def parenthesized_arguments(node)
274
+ padding = space_inside_parens? ? ' ' : ''
275
+
276
+ "(#{padding}#{node.arguments.source}#{padding})"
277
+ end
278
+
279
+ def space_inside_parens?
280
+ return false unless config.cop_enabled?('Layout/SpaceInsideParens')
281
+
282
+ config.for_cop('Layout/SpaceInsideParens')['EnforcedStyle'] != 'no_space'
221
283
  end
222
284
 
223
285
  def can_be_made_endless?(node)
224
- node.body && !node.body.begin_type? && !node.body.kwbegin_type?
286
+ return false unless node.body
287
+ return false if comment_in_discarded_range?(node)
288
+
289
+ endless_method_body?(node.body)
290
+ end
291
+
292
+ def single_line_when_made_endless?(node)
293
+ !endless_replacement(node).include?("\n")
225
294
  end
226
295
 
227
296
  def too_long_when_made_endless?(node)
228
297
  return false unless config.cop_enabled?('Layout/LineLength')
229
298
 
230
- offset = modifier_offset(node)
299
+ line_length(line_when_made_endless(node)) > max_line_length
300
+ end
301
+
302
+ def comment_in_discarded_range?(node)
303
+ discarded_ranges = [signature_to_body_range(node), body_to_end_range(node)]
231
304
 
232
- endless_replacement(node).length + offset > max_line_length
305
+ processed_source.each_comment_in_lines(node.first_line..node.last_line).any? do |comment|
306
+ discarded_ranges.any? { |range| range.contains?(comment.source_range) }
307
+ end
233
308
  end
234
309
 
235
- def modifier_offset(node)
236
- same_line?(node.parent, node) ? node.loc.column - node.parent.loc.column : 0
310
+ def line_when_made_endless(node)
311
+ keyword_element = node.loc.keyword
312
+ code_before = keyword_element.source_line[0...keyword_element.column]
313
+ end_element = node.loc.end
314
+ code_after = end_element.source_line[end_element.last_column..]
315
+
316
+ "#{code_before}#{endless_replacement(node)}#{code_after}"
237
317
  end
238
318
  end
239
319
  end
@@ -5,11 +5,15 @@ module RuboCop
5
5
  module Style
6
6
  # Checks for exact regexp match inside `Regexp` literals.
7
7
  #
8
+ # `=~` returns an index and `match` returns `MatchData`, so they are only
9
+ # autocorrected when their value is used as a condition. The same goes for
10
+ # safe navigation calls, which return `nil` for a `nil` receiver.
11
+ #
8
12
  # @example
9
13
  #
10
14
  # # bad
11
15
  # string =~ /\Astring\z/
12
- # string === /\Astring\z/
16
+ # /\Astring\z/ === string
13
17
  # string.match(/\Astring\z/)
14
18
  # string.match?(/\Astring\z/)
15
19
  #
@@ -27,14 +31,15 @@ module RuboCop
27
31
 
28
32
  MSG = 'Use `%<prefer>s`.'
29
33
  RESTRICT_ON_SEND = %i[=~ === !~ match match?].freeze
34
+ BOOLEAN_METHODS = %i[=== !~ match?].to_set.freeze
30
35
 
36
+ # `String#===` is plain equality, so `===` only matches with the regexp as its receiver.
31
37
  # @!method exact_regexp_match(node)
32
38
  def_node_matcher :exact_regexp_match, <<~PATTERN
33
- (call
34
- _ {:=~ :=== :!~ :match :match?}
35
- (regexp
36
- (str $_)
37
- (regopt)))
39
+ {
40
+ (call _ {:=~ :!~ :match :match?} (regexp (str $_) (regopt)))
41
+ (send (regexp (str $_) (regopt)) :=== _)
42
+ }
38
43
  PATTERN
39
44
 
40
45
  def on_send(node)
@@ -44,16 +49,48 @@ module RuboCop
44
49
  return unless exact_match_pattern?(parsed_regexp)
45
50
 
46
51
  string = escape_single_quotes(parsed_regexp[1].text)
47
- prefer = "#{receiver.source} #{new_method(node)} '#{string}'"
52
+ subject = node.method?(:===) ? node.first_argument : receiver
53
+ prefer = "#{subject.source} #{new_method(node)} '#{string}'"
48
54
 
49
55
  add_offense(node, message: format(MSG, prefer: prefer)) do |corrector|
50
- corrector.replace(node, prefer)
56
+ autocorrect(corrector, node, prefer)
51
57
  end
52
58
  end
53
59
  alias on_csend on_send
54
60
 
55
61
  private
56
62
 
63
+ def autocorrect(corrector, node, prefer)
64
+ return unless boolean_result?(node) || truthiness_only?(node)
65
+
66
+ corrector.replace(node, parenthesize?(node) ? "(#{prefer})" : prefer)
67
+ end
68
+
69
+ # `=~` returns an index, `match` returns `MatchData` and `&.` can return `nil`,
70
+ # so replacing them with `==` is only safe where the value is used as a condition.
71
+ def boolean_result?(node)
72
+ node.send_type? && BOOLEAN_METHODS.include?(node.method_name)
73
+ end
74
+
75
+ def truthiness_only?(node)
76
+ return true unless node.value_used?
77
+
78
+ parent = node.parent
79
+ if parent.type?(:begin, :and, :or)
80
+ truthiness_only?(parent)
81
+ elsif parent.type?(:if, :while, :until)
82
+ parent.condition.equal?(node)
83
+ else
84
+ parent.send_type? && parent.method?(:!)
85
+ end
86
+ end
87
+
88
+ def parenthesize?(node)
89
+ return false unless (parent = node.parent)&.call_type?
90
+
91
+ parent.receiver.equal?(node) || (parent.operator_method? && !parent.method?(:[]))
92
+ end
93
+
57
94
  # Escape characters that are special inside a single-quoted string so the
58
95
  # generated literal (e.g. for `/\Afoo'bar\z/`) stays valid Ruby.
59
96
  def escape_single_quotes(text)
@@ -50,8 +50,11 @@ module RuboCop
50
50
  (block $_ (args $...) (yield $...))
51
51
  PATTERN
52
52
 
53
+ # `Style/LambdaCall` rewrites the `call` that the block argument is appended to, and
54
+ # `Style/TrailingCommaInArguments` changes the trailing comma where it is appended,
55
+ # in the same pass.
53
56
  def self.autocorrect_incompatible_with
54
- [Lint::UnusedMethodArgument]
57
+ [Lint::UnusedMethodArgument, Style::LambdaCall, Style::TrailingCommaInArguments]
55
58
  end
56
59
 
57
60
  def initialize(config = nil, options = nil)
@@ -70,10 +73,13 @@ module RuboCop
70
73
  # this is not a valid ruby pattern, but can happen in haml or erb,
71
74
  # so this can cause crashes in haml_lint
72
75
  return unless def_node
76
+ return if argument_forwarding?(def_node)
73
77
 
74
78
  block_name = extract_block_name(def_node)
75
79
 
76
80
  add_offense(block_node) do |corrector|
81
+ next unless can_add_block_argument?(def_node, send_node, block_name)
82
+
77
83
  corrector.remove(block_body_range(block_node, send_node))
78
84
 
79
85
  add_block_argument(send_node, corrector, block_name)
@@ -84,6 +90,11 @@ module RuboCop
84
90
 
85
91
  private
86
92
 
93
+ # A block parameter can't be added next to `...`.
94
+ def argument_forwarding?(def_node)
95
+ def_node.arguments.any?(&:forward_arg_type?)
96
+ end
97
+
87
98
  def extract_block_name(def_node)
88
99
  if def_node.block_argument?
89
100
  def_node.last_argument.name
@@ -92,6 +103,27 @@ module RuboCop
92
103
  end
93
104
  end
94
105
 
106
+ def can_add_block_argument?(def_node, send_node, block_name)
107
+ return false if block_name_taken?(def_node, block_name)
108
+
109
+ !send_node.zsuper_type? || zsuper_arguments_forwardable?(def_node)
110
+ end
111
+
112
+ def block_name_taken?(def_node, block_name)
113
+ return false if def_node.block_argument?
114
+
115
+ def_node.each_descendant(:argument, :lvasgn).any? { |node| node.name.to_s == block_name }
116
+ end
117
+
118
+ # A destructuring argument can only be passed on as the original value,
119
+ # and anonymous arguments can only be forwarded since Ruby 3.2.
120
+ def zsuper_arguments_forwardable?(def_node)
121
+ def_node.arguments.none? do |arg|
122
+ arg.mlhs_type? ||
123
+ (arg.type?(:restarg, :kwrestarg) && !arg.name && target_ruby_version < 3.2)
124
+ end
125
+ end
126
+
95
127
  def yielding_arguments?(block_args, yield_args)
96
128
  yield_args = yield_args.dup.fill(
97
129
  nil,
@@ -110,6 +142,8 @@ module RuboCop
110
142
  insert_argument(node, corrector, block_name)
111
143
  elsif empty_arguments?(node)
112
144
  corrector.replace(node.arguments, "(&#{block_name})")
145
+ elsif index_call?(node)
146
+ corrector.replace(node.loc.selector, "[&#{block_name}]")
113
147
  elsif call_like?(node)
114
148
  correct_call_node(node, corrector, block_name)
115
149
  else
@@ -122,6 +156,10 @@ module RuboCop
122
156
  node.arguments.is_a?(RuboCop::AST::Node) && node.arguments.loc.begin
123
157
  end
124
158
 
159
+ def index_call?(node)
160
+ node.send_type? && node.method?(:[]) && !node.loc?(:dot)
161
+ end
162
+
125
163
  def call_like?(node)
126
164
  node.type?(:call, :zsuper, :super)
127
165
  end
@@ -141,19 +179,21 @@ module RuboCop
141
179
  else
142
180
  "&#{block_name}"
143
181
  end
144
- corrector.insert_after(node, "(#{new_arguments})")
145
- return unless node.parenthesized?
146
-
147
- args_begin = Util.args_begin(node)
148
- args_end = Util.args_end(node)
149
- range = range_between(args_begin.begin_pos, args_end.end_pos)
150
- corrector.remove(range)
182
+ if node.parenthesized?
183
+ corrector.replace(node.loc.begin.join(node.loc.end), "(#{new_arguments})")
184
+ else
185
+ corrector.insert_after(node, "(#{new_arguments})")
186
+ end
151
187
  end
152
188
 
153
189
  def build_new_arguments_for_zsuper(node)
154
190
  def_node = node.each_ancestor(:any_def).first
155
- def_node.arguments.map do |arg|
156
- arg.optarg_type? ? arg.node_parts[0] : arg.source
191
+ def_node.arguments.filter_map do |arg|
192
+ case arg.type
193
+ when :arg, :optarg then arg.name.to_s
194
+ when :kwarg, :kwoptarg then "#{arg.name}: #{arg.name}"
195
+ when :restarg, :kwrestarg then arg.source
196
+ end
157
197
  end
158
198
  end
159
199
 
@@ -54,7 +54,7 @@ module RuboCop
54
54
  @contain_dev_null_string_in_file = ast.each_descendant(:str).any? do |str|
55
55
  content = str.str_content
56
56
 
57
- valid_string?(content) && content.downcase == '/dev/null' # rubocop:disable Style/FileNull
57
+ valid_string?(content) && content.downcase == '/dev/null' # rubocop:disable Style/FileNull -- the cop needs the literal it is matching
58
58
  end
59
59
  end
60
60
 
@@ -62,7 +62,7 @@ module RuboCop
62
62
  value = node.value
63
63
  return unless valid_string?(value)
64
64
  return if acceptable?(node)
65
- return if value.downcase == 'nul' && !@contain_dev_null_string_in_file # rubocop:disable Style/FileNull
65
+ return if value.downcase == 'nul' && !@contain_dev_null_string_in_file # rubocop:disable Style/FileNull -- the cop needs the literal it is matching
66
66
  return unless REGEXP.match?(value)
67
67
 
68
68
  add_offense(node, message: format(MSG, source: value)) do |corrector|
@@ -97,6 +97,7 @@ module RuboCop
97
97
 
98
98
  def on_send(node)
99
99
  return unless offense_condition?(node)
100
+ return if comment_before_to_f?(node)
100
101
 
101
102
  add_offense(node) do |corrector|
102
103
  case style
@@ -107,7 +108,7 @@ module RuboCop
107
108
  remove_to_f_method(corrector, node.receiver)
108
109
  add_to_f_method(corrector, node.first_argument)
109
110
  when :fdiv
110
- correct_from_slash_to_fdiv(corrector, node, node.receiver, node.first_argument)
111
+ correct_from_slash_to_fdiv(corrector, node.receiver, node.first_argument)
111
112
  end
112
113
  end
113
114
  end
@@ -141,28 +142,41 @@ module RuboCop
141
142
  end
142
143
 
143
144
  def remove_to_f_method(corrector, send_node)
144
- corrector.remove(send_node.loc.dot)
145
- corrector.remove(send_node.loc.selector)
145
+ corrector.remove(send_node.receiver.source_range.end.join(send_node.loc.selector.end))
146
146
  end
147
147
 
148
- def correct_from_slash_to_fdiv(corrector, node, receiver, argument)
149
- receiver_source = extract_receiver_source(receiver)
150
- argument_source = extract_receiver_source(argument)
148
+ def comment_before_to_f?(node)
149
+ operands_losing_to_f(node).any? do |operand|
150
+ next false unless to_f_method?(operand)
151
151
 
152
- if argument.respond_to?(:parenthesized?) && !argument.parenthesized?
153
- argument_source = "(#{argument_source})"
152
+ range = operand.receiver.source_range.end.join(operand.loc.selector.begin)
153
+ processed_source.contains_comment?(range)
154
154
  end
155
-
156
- corrector.replace(node, "#{receiver_source}.fdiv#{argument_source}")
157
155
  end
158
156
 
159
- def extract_receiver_source(node)
160
- if node.send_type? && node.method?(:to_f)
161
- node.receiver.source
157
+ def operands_losing_to_f(node)
158
+ case style
159
+ when :left_coerce, :single_coerce
160
+ [node.first_argument]
161
+ when :right_coerce
162
+ [node.receiver]
162
163
  else
163
- node.source
164
+ [node.receiver, node.first_argument]
164
165
  end
165
166
  end
167
+
168
+ def correct_from_slash_to_fdiv(corrector, receiver, argument)
169
+ remove_to_f_method(corrector, receiver) if to_f_method?(receiver)
170
+
171
+ argument_parenthesized = argument.begin_type?
172
+ corrector.replace(
173
+ receiver.source_range.end.join(argument.source_range.begin),
174
+ argument_parenthesized ? '.fdiv' : '.fdiv('
175
+ )
176
+
177
+ remove_to_f_method(corrector, argument) if to_f_method?(argument)
178
+ corrector.insert_after(argument, ')') unless argument_parenthesized
179
+ end
166
180
  end
167
181
  end
168
182
  end
@@ -52,12 +52,21 @@ module RuboCop
52
52
  EACH_LENGTH = 'each'.length
53
53
  PREFER_EACH = 'Prefer `each` over `for`.'
54
54
  PREFER_FOR = 'Prefer `for` over `each`.'
55
+ IMPLICIT_PARAMETERS = %i[_1 _2 _3 _4 _5 _6 _7 _8 _9 it].to_set.freeze
56
+
57
+ # `Style/RedundantBegin` removes the `begin` of a block body in the same pass,
58
+ # leaving a `rescue` or `ensure` clause directly in the `for` body.
59
+ def self.autocorrect_incompatible_with
60
+ [Style::RedundantBegin]
61
+ end
55
62
 
56
63
  def on_for(node)
57
64
  if style == :each
58
65
  add_offense(node, message: PREFER_EACH) do |corrector|
59
- ForToEachCorrector.new(node).call(corrector)
60
66
  opposite_style_detected
67
+ next unless convertible_to_each?(node)
68
+
69
+ ForToEachCorrector.new(node).call(corrector)
61
70
  end
62
71
  else
63
72
  correct_style_detected
@@ -69,6 +78,7 @@ module RuboCop
69
78
 
70
79
  if style == :for
71
80
  return unless node.receiver
81
+ return if rescue_or_ensure_body?(node) || do_end_block_in_collection?(node)
72
82
 
73
83
  add_offense(node, message: PREFER_FOR) do |corrector|
74
84
  EachToForCorrector.new(node).call(corrector)
@@ -84,9 +94,42 @@ module RuboCop
84
94
 
85
95
  private
86
96
 
97
+ # The loop variable becomes a block parameter, which can only be a local
98
+ # variable, and the body can't refer to an implicit block parameter
99
+ # (`_1` or `it`) once the block has an explicit one.
100
+ def convertible_to_each?(node)
101
+ return false unless node.variable.each_node.all? { |n| n.type?(:lvasgn, :mlhs, :splat) }
102
+ return true unless node.body
103
+
104
+ node.body.each_node(:send, :lvar).none? { |ref| implicit_parameter?(ref, node) }
105
+ end
106
+
107
+ # Inside a numbered or `it` block, `_1` and `it` are local variables
108
+ # rather than method calls.
109
+ def implicit_parameter?(ref, for_node)
110
+ return false unless implicit_parameter_reference?(ref)
111
+
112
+ ref.each_ancestor.take_while { |ancestor| !ancestor.equal?(for_node) }
113
+ .none?(&:any_block_type?)
114
+ end
115
+
116
+ def implicit_parameter_reference?(ref)
117
+ return IMPLICIT_PARAMETERS.include?(ref.name) if ref.lvar_type?
118
+
119
+ IMPLICIT_PARAMETERS.include?(ref.method_name) && !ref.receiver && !ref.arguments?
120
+ end
121
+
87
122
  def suspect_enumerable?(node)
88
123
  node.multiline? && node.method?(:each) && !node.send_node.arguments?
89
124
  end
125
+
126
+ def rescue_or_ensure_body?(node)
127
+ node.body&.type?(:rescue, :ensure)
128
+ end
129
+
130
+ def do_end_block_in_collection?(node)
131
+ node.receiver.each_node(:any_block).any?(&:keywords?)
132
+ end
90
133
  end
91
134
  end
92
135
  end
@@ -69,9 +69,13 @@ module RuboCop
69
69
 
70
70
  # @!method variable_argument?(node)
71
71
  def_node_matcher :variable_argument?, <<~PATTERN
72
- (send {str dstr} :% #autocorrectable?)
72
+ (send {str dstr} :% !#autocorrectable?)
73
73
  PATTERN
74
74
 
75
+ def on_new_investigation
76
+ @corrected_ranges = nil
77
+ end
78
+
75
79
  def on_send(node)
76
80
  formatter(node) do |selector|
77
81
  detected_style = selector == :% ? :percent : selector
@@ -86,10 +90,12 @@ module RuboCop
86
90
 
87
91
  private
88
92
 
93
+ # Only an argument that is provably not an array can be corrected,
94
+ # because `String#%` spreads an array argument and `format` does not.
89
95
  def autocorrectable?(node)
90
- return true if node.lvar_type?
96
+ return true if node.literal?
91
97
 
92
- node.send_type? && !AUTOCORRECTABLE_METHODS.include?(node.method_name)
98
+ node.call_type? && AUTOCORRECTABLE_METHODS.include?(node.method_name)
93
99
  end
94
100
 
95
101
  def message(detected_style)
@@ -101,7 +107,9 @@ module RuboCop
101
107
  end
102
108
 
103
109
  def autocorrect(corrector, node)
104
- return if variable_argument?(node)
110
+ return if variable_argument?(node) || within_corrected_range?(node)
111
+
112
+ corrected_ranges << node.source_range
105
113
 
106
114
  case node.method_name
107
115
  when :%
@@ -116,6 +124,14 @@ module RuboCop
116
124
  end
117
125
  end
118
126
 
127
+ def within_corrected_range?(node)
128
+ corrected_ranges.any? { |range| range.contains?(node.source_range) }
129
+ end
130
+
131
+ def corrected_ranges
132
+ @corrected_ranges ||= []
133
+ end
134
+
119
135
  def autocorrect_from_percent(corrector, node)
120
136
  percent_rhs = node.first_argument
121
137
  args = case percent_rhs.type
@@ -146,14 +162,20 @@ module RuboCop
146
162
  def format_single_parameter(arg)
147
163
  # `format(fmt, *args)` is equivalent to `fmt % args`, so unwrap the splat
148
164
  # and render the argument it splats.
149
- return format_single_parameter(arg.children.first) if arg.splat_type?
165
+ return format_splatted_parameter(arg.children.first) if arg.splat_type?
150
166
 
151
167
  source = arg.source
152
- return "{ #{source} }" if arg.hash_type?
168
+ return "{ #{source} }" if arg.hash_type? && !arg.braces?
153
169
 
154
170
  requires_parentheses?(arg) ? "(#{source})" : source
155
171
  end
156
172
 
173
+ def format_splatted_parameter(arg)
174
+ return "#{arg.source}.to_a" if arg.hash_type?
175
+
176
+ format_single_parameter(arg)
177
+ end
178
+
157
179
  # An argument that binds looser than `%` (a ternary, range, assignment, or
158
180
  # operator call) must be parenthesized to keep its meaning.
159
181
  def requires_parentheses?(arg)
@@ -228,7 +228,7 @@ module RuboCop
228
228
  "Prefer #{message_text(style)} over #{message_text(detected_style)}."
229
229
  end
230
230
 
231
- # rubocop:disable-next Style/FormatStringToken
231
+ # rubocop:disable-next Style/FormatStringToken -- the cop needs the token style it is matching
232
232
  def message_text(style)
233
233
  {
234
234
  annotated: 'annotated tokens (like `%<foo>s`)',
@@ -142,7 +142,8 @@ module RuboCop
142
142
  end
143
143
 
144
144
  next_token = processed_source.tokens[token_number]
145
- if next_token&.text&.valid_encoding? && Encoding::ENCODING_PATTERN.match?(next_token.text)
145
+ if next_token&.text&.valid_encoding? &&
146
+ MagicComment.parse(next_token.text).encoding_specified?
146
147
  token = next_token
147
148
  end
148
149
 
@@ -109,6 +109,10 @@ module RuboCop
109
109
  MSG = 'Use a guard clause (`%<example>s`) instead of wrapping the ' \
110
110
  'code inside a conditional expression.'
111
111
 
112
+ def self.autocorrect_incompatible_with
113
+ [Style::MissingElse]
114
+ end
115
+
112
116
  def on_def(node)
113
117
  body = node.body
114
118
 
@@ -278,7 +282,15 @@ module RuboCop
278
282
  end
279
283
 
280
284
  def accepted_form?(node, ending: false)
281
- accepted_if?(node, ending) || node.condition.multiline? || node.parent&.assignment?
285
+ accepted_if?(node, ending) || node.condition.multiline? ||
286
+ node.parent&.assignment? || endless_method_body?(node)
287
+ end
288
+
289
+ # The body of an endless method is a single expression, so there is no
290
+ # room for a guard clause followed by the rest of the code.
291
+ def endless_method_body?(node)
292
+ parent = node.parent
293
+ parent&.any_def_type? && parent.endless?
282
294
  end
283
295
 
284
296
  def trivial?(node)