rubocop 1.91.0 → 1.92.1

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 (263) 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_loader.rb +6 -3
  14. data/lib/rubocop/config_loader_resolver.rb +37 -12
  15. data/lib/rubocop/config_obsoletion/removed_cop.rb +1 -1
  16. data/lib/rubocop/config_validator.rb +11 -0
  17. data/lib/rubocop/cop/autocorrect_logic.rb +10 -5
  18. data/lib/rubocop/cop/badge.rb +1 -1
  19. data/lib/rubocop/cop/base.rb +42 -21
  20. data/lib/rubocop/cop/bundler/gem_comment.rb +0 -1
  21. data/lib/rubocop/cop/bundler/ordered_gems.rb +0 -8
  22. data/lib/rubocop/cop/cop_documentation.rb +81 -0
  23. data/lib/rubocop/cop/cop_explanation.rb +140 -0
  24. data/lib/rubocop/cop/correctors/alignment_corrector.rb +13 -1
  25. data/lib/rubocop/cop/correctors/each_to_for_corrector.rb +37 -7
  26. data/lib/rubocop/cop/correctors/empty_line_corrector.rb +1 -1
  27. data/lib/rubocop/cop/correctors/lambda_literal_to_method_corrector.rb +25 -4
  28. data/lib/rubocop/cop/correctors/multiline_literal_brace_corrector.rb +34 -0
  29. data/lib/rubocop/cop/correctors/parentheses_corrector.rb +45 -8
  30. data/lib/rubocop/cop/documentation.rb +1 -1
  31. data/lib/rubocop/cop/gemspec/dependency_version.rb +0 -6
  32. data/lib/rubocop/cop/gemspec/ordered_dependencies.rb +0 -6
  33. data/lib/rubocop/cop/internal_affairs/node_matcher_directive.rb +2 -5
  34. data/lib/rubocop/cop/internal_affairs/node_type_group.rb +0 -7
  35. data/lib/rubocop/cop/internal_affairs/redundant_context_config_parameter.rb +1 -7
  36. data/lib/rubocop/cop/internal_affairs/style_detected_api_use.rb +1 -7
  37. data/lib/rubocop/cop/layout/argument_alignment.rb +5 -1
  38. data/lib/rubocop/cop/layout/block_alignment.rb +27 -0
  39. data/lib/rubocop/cop/layout/case_indentation.rb +25 -9
  40. data/lib/rubocop/cop/layout/class_structure.rb +29 -7
  41. data/lib/rubocop/cop/layout/def_end_alignment.rb +4 -0
  42. data/lib/rubocop/cop/layout/dot_position.rb +25 -15
  43. data/lib/rubocop/cop/layout/empty_line_after_multiline_condition.rb +21 -8
  44. data/lib/rubocop/cop/layout/empty_lines.rb +5 -2
  45. data/lib/rubocop/cop/layout/empty_lines_around_attribute_accessor.rb +1 -0
  46. data/lib/rubocop/cop/layout/empty_lines_around_block_body.rb +9 -3
  47. data/lib/rubocop/cop/layout/empty_lines_around_exception_handling_keywords.rb +23 -1
  48. data/lib/rubocop/cop/layout/end_alignment.rb +19 -17
  49. data/lib/rubocop/cop/layout/extra_spacing.rb +4 -0
  50. data/lib/rubocop/cop/layout/first_argument_indentation.rb +27 -1
  51. data/lib/rubocop/cop/layout/first_array_element_line_break.rb +15 -0
  52. data/lib/rubocop/cop/layout/first_hash_element_indentation.rb +16 -8
  53. data/lib/rubocop/cop/layout/first_method_argument_line_break.rb +6 -0
  54. data/lib/rubocop/cop/layout/first_method_parameter_line_break.rb +4 -0
  55. data/lib/rubocop/cop/layout/hash_alignment.rb +16 -12
  56. data/lib/rubocop/cop/layout/heredoc_argument_closing_parenthesis.rb +7 -3
  57. data/lib/rubocop/cop/layout/heredoc_indentation.rb +15 -0
  58. data/lib/rubocop/cop/layout/indentation_consistency.rb +22 -5
  59. data/lib/rubocop/cop/layout/indentation_style.rb +4 -1
  60. data/lib/rubocop/cop/layout/indentation_width.rb +2 -2
  61. data/lib/rubocop/cop/layout/line_continuation_spacing.rb +8 -1
  62. data/lib/rubocop/cop/layout/line_end_string_concatenation_indentation.rb +25 -9
  63. data/lib/rubocop/cop/layout/line_length.rb +153 -21
  64. data/lib/rubocop/cop/layout/multiline_array_brace_layout.rb +12 -0
  65. data/lib/rubocop/cop/layout/multiline_block_layout.rb +8 -1
  66. data/lib/rubocop/cop/layout/multiline_hash_brace_layout.rb +9 -0
  67. data/lib/rubocop/cop/layout/multiline_method_call_brace_layout.rb +6 -0
  68. data/lib/rubocop/cop/layout/multiline_method_call_indentation.rb +30 -5
  69. data/lib/rubocop/cop/layout/multiline_method_definition_brace_layout.rb +4 -0
  70. data/lib/rubocop/cop/layout/redundant_line_break.rb +22 -1
  71. data/lib/rubocop/cop/layout/rescue_ensure_alignment.rb +15 -2
  72. data/lib/rubocop/cop/layout/single_line_block_chain.rb +10 -2
  73. data/lib/rubocop/cop/layout/space_after_not.rb +5 -0
  74. data/lib/rubocop/cop/layout/space_around_block_parameters.rb +6 -11
  75. data/lib/rubocop/cop/layout/space_around_keyword.rb +10 -0
  76. data/lib/rubocop/cop/layout/space_before_block_braces.rb +4 -1
  77. data/lib/rubocop/cop/layout/space_inside_array_literal_brackets.rb +15 -2
  78. data/lib/rubocop/cop/layout/space_inside_block_braces.rb +31 -8
  79. data/lib/rubocop/cop/layout/space_inside_reference_brackets.rb +2 -12
  80. data/lib/rubocop/cop/lint/ambiguous_operator.rb +1 -1
  81. data/lib/rubocop/cop/lint/ambiguous_operator_precedence.rb +2 -1
  82. data/lib/rubocop/cop/lint/ambiguous_regexp_literal.rb +6 -1
  83. data/lib/rubocop/cop/lint/empty_conditional_body.rb +3 -1
  84. data/lib/rubocop/cop/lint/heredoc_method_call_position.rb +4 -0
  85. data/lib/rubocop/cop/lint/literal_as_condition.rb +15 -0
  86. data/lib/rubocop/cop/lint/literal_in_interpolation.rb +63 -19
  87. data/lib/rubocop/cop/lint/loop.rb +24 -2
  88. data/lib/rubocop/cop/lint/misplaced_magic_comment.rb +71 -11
  89. data/lib/rubocop/cop/lint/missing_cop_enable_directive.rb +18 -3
  90. data/lib/rubocop/cop/lint/number_conversion.rb +3 -1
  91. data/lib/rubocop/cop/lint/redundant_cop_disable_directive.rb +12 -1
  92. data/lib/rubocop/cop/lint/redundant_dir_glob_sort.rb +13 -2
  93. data/lib/rubocop/cop/lint/safe_navigation_consistency.rb +47 -1
  94. data/lib/rubocop/cop/lint/symbol_conversion.rb +1 -0
  95. data/lib/rubocop/cop/lint/unescaped_bracket_in_regexp.rb +18 -1
  96. data/lib/rubocop/cop/lint/useless_assignment.rb +14 -7
  97. data/lib/rubocop/cop/lint/useless_setter_call.rb +7 -4
  98. data/lib/rubocop/cop/lint/void.rb +8 -1
  99. data/lib/rubocop/cop/metrics/class_length.rb +4 -2
  100. data/lib/rubocop/cop/metrics/cyclomatic_complexity.rb +0 -4
  101. data/lib/rubocop/cop/metrics/module_length.rb +4 -2
  102. data/lib/rubocop/cop/mixin/alignment.rb +4 -0
  103. data/lib/rubocop/cop/mixin/check_line_breakable.rb +23 -19
  104. data/lib/rubocop/cop/mixin/check_single_line_suitability.rb +8 -2
  105. data/lib/rubocop/cop/mixin/code_length.rb +10 -2
  106. data/lib/rubocop/cop/mixin/empty_lines_around_body.rb +12 -1
  107. data/lib/rubocop/cop/mixin/endless_method_body_help.rb +105 -0
  108. data/lib/rubocop/cop/mixin/forbidden_identifiers.rb +2 -2
  109. data/lib/rubocop/cop/mixin/hash_alignment_styles.rb +24 -5
  110. data/lib/rubocop/cop/mixin/hash_shorthand_syntax.rb +26 -2
  111. data/lib/rubocop/cop/mixin/min_branches_count.rb +3 -1
  112. data/lib/rubocop/cop/mixin/multiline_element_line_breaks.rb +11 -1
  113. data/lib/rubocop/cop/mixin/multiline_literal_brace_layout.rb +9 -0
  114. data/lib/rubocop/cop/mixin/ordered_gem_node.rb +1 -1
  115. data/lib/rubocop/cop/mixin/pattern_matching_help.rb +35 -0
  116. data/lib/rubocop/cop/mixin/preceding_following_alignment.rb +17 -2
  117. data/lib/rubocop/cop/mixin/project_index_help.rb +15 -0
  118. data/lib/rubocop/cop/mixin/reparsed_equivalence.rb +13 -3
  119. data/lib/rubocop/cop/mixin/statement_modifier.rb +25 -4
  120. data/lib/rubocop/cop/mixin/visibility_help.rb +2 -1
  121. data/lib/rubocop/cop/mixin.rb +2 -0
  122. data/lib/rubocop/cop/naming/accessor_method_name.rb +0 -16
  123. data/lib/rubocop/cop/naming/block_forwarding.rb +1 -1
  124. data/lib/rubocop/cop/naming/heredoc_delimiter_case.rb +9 -2
  125. data/lib/rubocop/cop/naming/heredoc_delimiter_naming.rb +5 -3
  126. data/lib/rubocop/cop/naming/predicate_prefix.rb +7 -22
  127. data/lib/rubocop/cop/naming/rescued_exceptions_variable_name.rb +1 -1
  128. data/lib/rubocop/cop/offense.rb +59 -3
  129. data/lib/rubocop/cop/registry.rb +1 -1
  130. data/lib/rubocop/cop/security/json_load.rb +16 -4
  131. data/lib/rubocop/cop/style/access_modifier_declarations.rb +37 -2
  132. data/lib/rubocop/cop/style/alias.rb +18 -5
  133. data/lib/rubocop/cop/style/ambiguous_endless_method_definition.rb +4 -3
  134. data/lib/rubocop/cop/style/and_or.rb +6 -2
  135. data/lib/rubocop/cop/style/arguments_forwarding.rb +32 -3
  136. data/lib/rubocop/cop/style/attr.rb +3 -4
  137. data/lib/rubocop/cop/style/bitwise_predicate.rb +6 -4
  138. data/lib/rubocop/cop/style/block_delimiters.rb +35 -3
  139. data/lib/rubocop/cop/style/case_like_if.rb +6 -3
  140. data/lib/rubocop/cop/style/class_and_module_children.rb +56 -16
  141. data/lib/rubocop/cop/style/class_methods_definitions.rb +15 -0
  142. data/lib/rubocop/cop/style/collection_querying.rb +79 -46
  143. data/lib/rubocop/cop/style/combinable_loops.rb +89 -11
  144. data/lib/rubocop/cop/style/concat_array_literals.rb +58 -7
  145. data/lib/rubocop/cop/style/conditional_assignment.rb +4 -0
  146. data/lib/rubocop/cop/style/disable_cops_within_source_code_directive.rb +65 -9
  147. data/lib/rubocop/cop/style/double_negation.rb +11 -0
  148. data/lib/rubocop/cop/style/empty_literal.rb +1 -1
  149. data/lib/rubocop/cop/style/empty_method.rb +3 -1
  150. data/lib/rubocop/cop/style/empty_string_inside_interpolation.rb +53 -11
  151. data/lib/rubocop/cop/style/endless_method.rb +71 -6
  152. data/lib/rubocop/cop/style/exact_regexp_match.rb +45 -8
  153. data/lib/rubocop/cop/style/explicit_block_argument.rb +50 -10
  154. data/lib/rubocop/cop/style/float_division.rb +22 -2
  155. data/lib/rubocop/cop/style/for.rb +53 -2
  156. data/lib/rubocop/cop/style/format_string.rb +28 -6
  157. data/lib/rubocop/cop/style/frozen_string_literal_comment.rb +2 -1
  158. data/lib/rubocop/cop/style/guard_clause.rb +31 -22
  159. data/lib/rubocop/cop/style/hash_as_last_array_item.rb +4 -0
  160. data/lib/rubocop/cop/style/hash_conversion.rb +28 -8
  161. data/lib/rubocop/cop/style/hash_lookup_method.rb +32 -9
  162. data/lib/rubocop/cop/style/hash_syntax.rb +16 -3
  163. data/lib/rubocop/cop/style/identical_conditional_branches.rb +22 -3
  164. data/lib/rubocop/cop/style/if_inside_else.rb +21 -1
  165. data/lib/rubocop/cop/style/if_unless_modifier.rb +32 -26
  166. data/lib/rubocop/cop/style/if_unless_modifier_of_if_unless.rb +4 -0
  167. data/lib/rubocop/cop/style/if_with_semicolon.rb +47 -4
  168. data/lib/rubocop/cop/style/inverse_methods.rb +24 -2
  169. data/lib/rubocop/cop/style/it_block_parameter.rb +94 -4
  170. data/lib/rubocop/cop/style/keyword_parameters_order.rb +3 -1
  171. data/lib/rubocop/cop/style/lambda.rb +34 -1
  172. data/lib/rubocop/cop/style/lambda_call.rb +7 -2
  173. data/lib/rubocop/cop/style/map_into_array.rb +1 -2
  174. data/lib/rubocop/cop/style/method_call_with_args_parentheses/omit_parentheses.rb +80 -13
  175. data/lib/rubocop/cop/style/method_call_with_args_parentheses.rb +25 -2
  176. data/lib/rubocop/cop/style/method_call_without_args_parentheses.rb +26 -3
  177. data/lib/rubocop/cop/style/method_def_parentheses.rb +29 -3
  178. data/lib/rubocop/cop/style/missing_else.rb +7 -1
  179. data/lib/rubocop/cop/style/mixin_grouping.rb +12 -3
  180. data/lib/rubocop/cop/style/multiline_if_modifier.rb +45 -5
  181. data/lib/rubocop/cop/style/multiline_in_pattern_then.rb +7 -0
  182. data/lib/rubocop/cop/style/multiline_method_signature.rb +2 -5
  183. data/lib/rubocop/cop/style/multiline_ternary_operator.rb +7 -3
  184. data/lib/rubocop/cop/style/multiline_when_then.rb +7 -0
  185. data/lib/rubocop/cop/style/mutable_constant.rb +52 -7
  186. data/lib/rubocop/cop/style/negated_if_else_condition.rb +2 -1
  187. data/lib/rubocop/cop/style/negative_array_index.rb +9 -1
  188. data/lib/rubocop/cop/style/nested_modifier.rb +27 -8
  189. data/lib/rubocop/cop/style/nested_ternary_operator.rb +26 -8
  190. data/lib/rubocop/cop/style/next.rb +24 -10
  191. data/lib/rubocop/cop/style/nil_comparison.rb +16 -4
  192. data/lib/rubocop/cop/style/non_nil_check.rb +1 -1
  193. data/lib/rubocop/cop/style/not.rb +10 -5
  194. data/lib/rubocop/cop/style/numeric_predicate.rb +21 -5
  195. data/lib/rubocop/cop/style/one_line_conditional.rb +13 -2
  196. data/lib/rubocop/cop/style/parentheses_around_condition.rb +1 -1
  197. data/lib/rubocop/cop/style/percent_literal_delimiters.rb +14 -3
  198. data/lib/rubocop/cop/style/raise_args.rb +19 -9
  199. data/lib/rubocop/cop/style/reduce_to_hash.rb +17 -1
  200. data/lib/rubocop/cop/style/redundant_begin.rb +22 -4
  201. data/lib/rubocop/cop/style/redundant_condition.rb +30 -13
  202. data/lib/rubocop/cop/style/redundant_double_splat_hash_braces.rb +6 -0
  203. data/lib/rubocop/cop/style/redundant_exception.rb +15 -3
  204. data/lib/rubocop/cop/style/redundant_fetch_block.rb +9 -1
  205. data/lib/rubocop/cop/style/redundant_freeze.rb +11 -3
  206. data/lib/rubocop/cop/style/redundant_interpolation_unfreeze.rb +24 -1
  207. data/lib/rubocop/cop/style/redundant_parentheses.rb +12 -1
  208. data/lib/rubocop/cop/style/redundant_regexp_argument.rb +7 -0
  209. data/lib/rubocop/cop/style/redundant_regexp_constructor.rb +20 -0
  210. data/lib/rubocop/cop/style/redundant_regexp_escape.rb +4 -0
  211. data/lib/rubocop/cop/style/redundant_self_assignment_branch.rb +28 -0
  212. data/lib/rubocop/cop/style/redundant_sort.rb +38 -4
  213. data/lib/rubocop/cop/style/regexp_literal.rb +75 -26
  214. data/lib/rubocop/cop/style/require_order.rb +25 -8
  215. data/lib/rubocop/cop/style/rescue_modifier.rb +11 -9
  216. data/lib/rubocop/cop/style/return_nil.rb +17 -2
  217. data/lib/rubocop/cop/style/return_nil_in_predicate_method_definition.rb +11 -1
  218. data/lib/rubocop/cop/style/sample.rb +9 -8
  219. data/lib/rubocop/cop/style/self_assignment.rb +12 -15
  220. data/lib/rubocop/cop/style/semicolon.rb +16 -5
  221. data/lib/rubocop/cop/style/send_with_literal_method_name.rb +4 -0
  222. data/lib/rubocop/cop/style/single_line_methods.rb +21 -4
  223. data/lib/rubocop/cop/style/stabby_lambda_parentheses.rb +29 -5
  224. data/lib/rubocop/cop/style/string_concatenation.rb +9 -3
  225. data/lib/rubocop/cop/style/struct_inheritance.rb +6 -0
  226. data/lib/rubocop/cop/style/super_arguments.rb +3 -0
  227. data/lib/rubocop/cop/style/symbol_proc.rb +24 -6
  228. data/lib/rubocop/cop/style/ternary_parentheses.rb +23 -1
  229. data/lib/rubocop/cop/style/trailing_underscore_variable.rb +23 -19
  230. data/lib/rubocop/cop/style/while_until_modifier.rb +1 -1
  231. data/lib/rubocop/cop/style/yoda_condition.rb +24 -9
  232. data/lib/rubocop/cop/style/yoda_expression.rb +10 -1
  233. data/lib/rubocop/cop/team.rb +10 -5
  234. data/lib/rubocop/cop/variable_force/assignment.rb +1 -1
  235. data/lib/rubocop/cop/variable_force/variable.rb +14 -1
  236. data/lib/rubocop/formatter/base_formatter.rb +22 -0
  237. data/lib/rubocop/formatter/formatter_set.rb +4 -0
  238. data/lib/rubocop/formatter/json_formatter.rb +71 -1
  239. data/lib/rubocop/formatter/offense_count_formatter.rb +3 -1
  240. data/lib/rubocop/formatter/pacman_formatter.rb +3 -1
  241. data/lib/rubocop/formatter/sarif_formatter.rb +12 -7
  242. data/lib/rubocop/formatter/worst_offenders_formatter.rb +3 -1
  243. data/lib/rubocop/lsp/routes.rb +23 -23
  244. data/lib/rubocop/lsp/runtime.rb +47 -11
  245. data/lib/rubocop/lsp/stdin_runner.rb +11 -5
  246. data/lib/rubocop/lsp.rb +5 -2
  247. data/lib/rubocop/mcp/autocorrection_request.rb +52 -0
  248. data/lib/rubocop/mcp/explain_tool.rb +68 -0
  249. data/lib/rubocop/mcp/scope.rb +65 -0
  250. data/lib/rubocop/mcp/server.rb +184 -76
  251. data/lib/rubocop/mcp/source_files.rb +35 -0
  252. data/lib/rubocop/offense_limit.rb +30 -0
  253. data/lib/rubocop/options.rb +45 -10
  254. data/lib/rubocop/plugin/configuration_integrator.rb +12 -5
  255. data/lib/rubocop/remote_config.rb +4 -1
  256. data/lib/rubocop/result_cache.rb +20 -13
  257. data/lib/rubocop/runner.rb +109 -41
  258. data/lib/rubocop/server/cache.rb +6 -6
  259. data/lib/rubocop/target_ruby.rb +21 -9
  260. data/lib/rubocop/util.rb +69 -0
  261. data/lib/rubocop/version.rb +1 -1
  262. data/lib/rubocop.rb +5 -0
  263. metadata +16 -5
@@ -23,6 +23,7 @@ module RuboCop
23
23
  # list.push(qux, quux, corge)
24
24
  #
25
25
  class ConcatArrayLiterals < Base
26
+ include RangeHelp
26
27
  extend AutoCorrector
27
28
 
28
29
  MSG = 'Use `%<prefer>s` instead of `%<current>s`.'
@@ -30,6 +31,14 @@ module RuboCop
30
31
  'Use `push` with elements as arguments without array brackets instead of `%<current>s`.'
31
32
  RESTRICT_ON_SEND = %i[concat].freeze
32
33
 
34
+ # `Style/MethodCallWithArgsParentheses` removing the parentheses of the `push(...)`
35
+ # this cop writes in the same pass drops the line continuation a multiline call needs.
36
+ # `Style/TrailingCommaInArguments` adds a comma after the array literal this cop
37
+ # replaces with its elements in the same pass.
38
+ def self.autocorrect_incompatible_with
39
+ [Style::MethodCallWithArgsParentheses, Style::TrailingCommaInArguments]
40
+ end
41
+
33
42
  # rubocop:disable-next Metrics
34
43
  def on_send(node)
35
44
  return if node.arguments.empty?
@@ -54,17 +63,13 @@ module RuboCop
54
63
  if use_percent_literal
55
64
  next unless prefer
56
65
 
57
- corrector.replace(offense, prefer)
66
+ rebuild_call(corrector, offense, prefer)
58
67
  elsif node.arguments.any? { |argument| argument.children.empty? }
59
68
  # In-place bracket removal would leave dangling commas (e.g.
60
69
  # `concat([], [b])` -> `push(, b)`), so rebuild the call instead.
61
- corrector.replace(offense, preferred_method(node))
70
+ rebuild_call(corrector, offense, preferred_method(node))
62
71
  else
63
- corrector.replace(node.loc.selector, 'push')
64
- node.arguments.each do |argument|
65
- corrector.remove(argument.loc.begin)
66
- corrector.remove(argument.loc.end)
67
- end
72
+ remove_brackets(corrector, node)
68
73
  end
69
74
  end
70
75
  end
@@ -94,6 +99,52 @@ module RuboCop
94
99
  arg.children.all? { |child| child.type?(:str, :sym) }
95
100
  end
96
101
  end
102
+
103
+ def rebuild_call(corrector, offense, replacement)
104
+ # The comments inside the call would be lost.
105
+ return if comment_in?(offense)
106
+
107
+ corrector.replace(offense, replacement)
108
+ end
109
+
110
+ def comment_in?(range)
111
+ processed_source.each_comment_in_lines(range.line..range.last_line).any? do |comment|
112
+ range.contains?(comment.source_range)
113
+ end
114
+ end
115
+
116
+ def remove_brackets(corrector, node)
117
+ closing_ranges = node.arguments.map { |argument| closing_bracket_range(node, argument) }
118
+ # A comment or a heredoc body there can't be moved out of the way.
119
+ return unless closing_ranges.all? { |range| range.source.match?(/\A[\s,]*\]\z/) }
120
+
121
+ replace_selector(corrector, node)
122
+
123
+ node.arguments.zip(closing_ranges) do |argument, closing_range|
124
+ corrector.remove(argument.loc.begin)
125
+ corrector.remove(closing_range)
126
+ end
127
+ end
128
+
129
+ # Whatever sits between the last element and the `]` of an array that isn't the
130
+ # last argument would end up before the following comma (a newline before it is a
131
+ # syntax error), so it's removed along with the `]`.
132
+ def closing_bracket_range(node, argument)
133
+ return argument.loc.end if argument.equal?(node.last_argument)
134
+
135
+ range_between(argument.children.last.source_range.end_pos, argument.loc.end.end_pos)
136
+ end
137
+
138
+ def replace_selector(corrector, node)
139
+ if node.parenthesized? || node.single_line?
140
+ corrector.replace(node.loc.selector, 'push')
141
+ else
142
+ range = node.loc.selector.join(node.first_argument.source_range.begin)
143
+
144
+ corrector.replace(range, 'push(')
145
+ corrector.insert_after(node.last_argument.loc.end, ')')
146
+ end
147
+ end
97
148
  end
98
149
  end
99
150
  end
@@ -223,6 +223,10 @@ module RuboCop
223
223
  }
224
224
  PATTERN
225
225
 
226
+ def self.autocorrect_incompatible_with
227
+ [Style::MethodCallWithArgsParentheses]
228
+ end
229
+
226
230
  ASSIGNMENT_TYPES.each do |type|
227
231
  define_method :"on_#{type}" do |node|
228
232
  return if part_of_ignored_node?(node)
@@ -33,7 +33,8 @@ module RuboCop
33
33
  # a `--` trailing justification comment is allowed, so a team can require
34
34
  # every disable to be documented instead of banning them outright. Enable
35
35
  # directives are not checked in this mode, since they end a suppression
36
- # rather than start one.
36
+ # rather than start one. Offenses in this mode are not autocorrected,
37
+ # since only a human can supply the missing justification.
37
38
  #
38
39
  # This cop cannot be disabled via directive comments when it is explicitly
39
40
  # enabled with `Enabled: true`. This prevents users from bypassing the cop
@@ -99,6 +100,8 @@ module RuboCop
99
100
  'comment are not permitted.'
100
101
 
101
102
  def on_new_investigation
103
+ @matching_pops = nil
104
+
102
105
  processed_source.comments.each do |comment|
103
106
  directive = DirectiveComment.new(comment)
104
107
  next if ignored?(directive)
@@ -108,7 +111,7 @@ module RuboCop
108
111
 
109
112
  next unless disallowed_cops.any?
110
113
 
111
- register_offense(comment, directive_cops, disallowed_cops)
114
+ register_offense(directive, directive_cops, disallowed_cops)
112
115
  end
113
116
  end
114
117
 
@@ -126,19 +129,61 @@ module RuboCop
126
129
  end
127
130
  end
128
131
 
129
- def register_offense(comment, directive_cops, disallowed_cops)
130
- add_offense(comment, message: offense_message(disallowed_cops)) do |corrector|
131
- replacement = ''
132
+ def register_offense(directive, directive_cops, disallowed_cops)
133
+ comment = directive.comment
132
134
 
133
- if directive_cops.length != disallowed_cops.length
134
- replacement = comment.text.sub(/#{Regexp.union(disallowed_cops)},?\s*/, '')
135
- .sub(/,\s*$/, '')
135
+ add_offense(comment, message: offense_message(disallowed_cops)) do |corrector|
136
+ # The remedy in `AllowWithReason` mode is to write the missing `--` justification,
137
+ # which cannot be autocorrected. Removing the directive would also unsuppress
138
+ # the disabled cops and let their autocorrections rewrite the annotated code.
139
+ next if allow_with_reason?
140
+
141
+ if directive_cops.length == disallowed_cops.length
142
+ remove_directive(corrector, directive)
143
+ elsif signed_args?(directive)
144
+ remove_signed_args(corrector, directive, disallowed_cops)
145
+ else
146
+ remove_cops(corrector, comment, disallowed_cops)
136
147
  end
148
+ end
149
+ end
150
+
151
+ # A `push` without its `pop` leaves an orphan `pop` behind, so the pair goes together.
152
+ def remove_directive(corrector, directive)
153
+ corrector.replace(directive.comment, '')
154
+ return unless directive.push? && (pop = matching_pop(directive.comment))
155
+
156
+ corrector.replace(pop, '')
157
+ end
158
+
159
+ def matching_pop(push_comment)
160
+ @matching_pops ||= pair_pushes_with_pops
161
+ @matching_pops[push_comment]
162
+ end
137
163
 
138
- corrector.replace(comment, replacement)
164
+ def pair_pushes_with_pops
165
+ pushes = []
166
+ processed_source.comments.each_with_object({}) do |comment, pairs|
167
+ directive = DirectiveComment.new(comment)
168
+ if directive.push?
169
+ pushes.push(comment)
170
+ elsif directive.pop? && (push = pushes.pop)
171
+ pairs[push] = comment
172
+ end
139
173
  end
140
174
  end
141
175
 
176
+ def remove_signed_args(corrector, directive, disallowed_cops)
177
+ range = directive.range
178
+ names = Regexp.union(disallowed_cops)
179
+ corrector.replace(range, range.source.gsub(/\s+[+-]#{names}(?!\S)/, ''))
180
+ end
181
+
182
+ def remove_cops(corrector, comment, disallowed_cops)
183
+ corrector.replace(comment, comment.text.sub(/#{Regexp.union(disallowed_cops)},?\s*/, '')
184
+ .sub(/,\s*$/, ''))
185
+ end
186
+
142
187
  def offense_message(disallowed_cops)
143
188
  if allow_with_reason?
144
189
  MSG_MISSING_REASON
@@ -150,10 +195,16 @@ module RuboCop
150
195
  end
151
196
 
152
197
  def directive_cops(directive)
198
+ return directive.signed_args.values.flatten if signed_args?(directive)
199
+
153
200
  match_captures = directive.match_captures
154
201
  match_captures && match_captures[1] ? match_captures[1].split(',').map(&:strip) : []
155
202
  end
156
203
 
204
+ def signed_args?(directive)
205
+ directive.push? || directive.next?
206
+ end
207
+
157
208
  def ignored?(directive)
158
209
  return true if exempt_mode?(directive)
159
210
 
@@ -169,6 +220,11 @@ module RuboCop
169
220
  end
170
221
 
171
222
  def allow_with_reason?
223
+ # TODO: The obsolete `AllowTrailingComment` is honored for backward compatibility
224
+ # and will be removed in RuboCop 2.0.
225
+ allow_trailing_comment = cop_config['AllowTrailingComment'] # rubocop:disable InternalAffairs/UndefinedConfig -- obsolete key, gone from default.yml on purpose
226
+ return allow_trailing_comment unless allow_trailing_comment.nil?
227
+
172
228
  cop_config['AllowWithReason']
173
229
  end
174
230
 
@@ -70,6 +70,7 @@ module RuboCop
70
70
 
71
71
  def on_send(node)
72
72
  return unless double_negative?(node) && node.prefix_bang?
73
+ return if part_of_longer_negation?(node)
73
74
  return if style == :allowed_in_returns && allowed_in_returns?(node)
74
75
 
75
76
  location = node.loc.selector
@@ -81,10 +82,20 @@ module RuboCop
81
82
 
82
83
  private
83
84
 
85
+ def part_of_longer_negation?(node)
86
+ return true if negation?(node.parent) && node.parent.receiver.equal?(node)
87
+
88
+ negation?(node.receiver.receiver)
89
+ end
90
+
84
91
  def allowed_in_returns?(node)
85
92
  node.parent&.return_type? || end_of_method_definition?(node)
86
93
  end
87
94
 
95
+ def negation?(node)
96
+ node&.send_type? && node.method?(:!)
97
+ end
98
+
88
99
  def end_of_method_definition?(node)
89
100
  return false unless (def_node = find_def_node_from_ascendant(node))
90
101
 
@@ -103,7 +103,7 @@ module RuboCop
103
103
  end
104
104
 
105
105
  def replacement_range(node)
106
- if hash_node(node) && first_argument_unparenthesized?(node)
106
+ if offense_hash_node?(node) && first_argument_unparenthesized?(node)
107
107
  # `some_method {}` is not same as `some_method Hash.new`
108
108
  # because the braces are interpreted as a block. We will have
109
109
  # to rewrite the arguments to wrap them in parenthesis.
@@ -102,8 +102,10 @@ module RuboCop
102
102
  compact_style? ? '; ' : "\n#{indent}"
103
103
  end
104
104
 
105
+ # A method with an argument spanning multiple lines (e.g. a hash default
106
+ # value) can't be put on a single line, so it's as compact as it gets.
105
107
  def compact?(node)
106
- node.single_line?
108
+ node.single_line? || node.arguments.any?(&:multiline?)
107
109
  end
108
110
 
109
111
  def expanded?(node)
@@ -45,6 +45,8 @@ module RuboCop
45
45
  MSG_TRAILING_CONDITIONAL = 'Do not use trailing conditionals in string interpolation.'
46
46
  MSG_TERNARY = 'Do not return empty strings in string interpolation.'
47
47
 
48
+ INTERPOLATED_TYPES = %i[dstr dsym regexp xstr].freeze
49
+
48
50
  def on_interpolation(node)
49
51
  node.each_child_node(:if) do |child_node|
50
52
  if style == :trailing_conditional
@@ -63,28 +65,54 @@ module RuboCop
63
65
  return if child_node.modifier_form?
64
66
 
65
67
  if empty_if_outcome?(child_node)
66
- ternary_style_autocorrect(child_node, child_node.else_branch.source, 'unless')
68
+ ternary_style_autocorrect(child_node, child_node.else_branch, 'unless')
67
69
  end
68
70
 
69
71
  return unless empty_else_outcome?(child_node)
70
72
 
71
- ternary_style_autocorrect(child_node, child_node.if_branch.source, 'if')
73
+ ternary_style_autocorrect(child_node, child_node.if_branch, 'if')
72
74
  end
73
75
 
74
76
  def ternary_correction(node, child_node)
75
77
  return unless child_node.modifier_form?
76
78
 
77
- ternary_component = if child_node.unless?
78
- "'' : #{child_node.if_branch.source}"
79
- else
80
- "#{child_node.if_branch.source} : ''"
81
- end
82
-
83
79
  add_offense(node, message: MSG_TRAILING_CONDITIONAL) do |corrector|
84
- corrector.replace(node, "\#{#{child_node.condition.source} ? #{ternary_component}}")
80
+ next if nested_trailing_conditional?(child_node)
81
+
82
+ autocorrect_to_ternary(corrector, child_node)
83
+ end
84
+ end
85
+
86
+ def nested_trailing_conditional?(if_node)
87
+ if_node.each_descendant(:if).any? do |node|
88
+ node.modifier_form? && node.parent.parent&.type?(*INTERPOLATED_TYPES)
85
89
  end
86
90
  end
87
91
 
92
+ def autocorrect_to_ternary(corrector, if_node)
93
+ condition = if_node.condition.source
94
+ branch = ternary_branch_source(if_node.if_branch)
95
+ outcomes = if_node.unless? ? "'' : #{branch}" : "#{branch} : ''"
96
+
97
+ corrector.replace(if_node, "#{condition} ? #{outcomes}")
98
+ end
99
+
100
+ def ternary_branch_source(branch)
101
+ return branch.source unless command_call?(branch)
102
+
103
+ arguments = branch.first_argument.source_range.join(branch.last_argument.source_range)
104
+ method = branch.source_range.begin.join(arguments.begin).source.rstrip
105
+
106
+ "#{method}(#{arguments.source})"
107
+ end
108
+
109
+ def command_call?(node)
110
+ return false unless node.type?(:call, :super, :yield) && node.arguments?
111
+ return false if node.call_type? && (node.operator_method? || node.setter_method?)
112
+
113
+ !node.parenthesized?
114
+ end
115
+
88
116
  def empty_if_outcome?(node)
89
117
  empty_branch_outcome?(node.if_branch)
90
118
  end
@@ -99,9 +127,23 @@ module RuboCop
99
127
  branch.nil_type? || (branch.str_type? && branch.value.empty?)
100
128
  end
101
129
 
102
- def ternary_style_autocorrect(node, outcome, condition)
130
+ def ternary_style_autocorrect(node, outcome, keyword)
103
131
  add_offense(node, message: MSG_TERNARY) do |corrector|
104
- corrector.replace(node, "#{outcome} #{condition} #{node.condition.source}")
132
+ next if empty_string_conditional_in_condition?(node)
133
+
134
+ head = node.source_range.begin.join(outcome.source_range.begin)
135
+ tail = outcome.source_range.end.join(node.source_range.end)
136
+
137
+ corrector.remove(head)
138
+ corrector.replace(tail, " #{keyword} #{node.condition.source}")
139
+ end
140
+ end
141
+
142
+ def empty_string_conditional_in_condition?(if_node)
143
+ if_node.condition.each_descendant(:if).any? do |node|
144
+ next false if node.modifier_form? || !node.parent.parent&.type?(*INTERPOLATED_TYPES)
145
+
146
+ empty_if_outcome?(node) || empty_else_outcome?(node)
105
147
  end
106
148
  end
107
149
  end
@@ -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,16 @@ 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
+ # `Style/GuardClause` turns a single `if` body into several statements,
151
+ # which an endless definition cannot hold.
152
+ # `Style/RaiseArgs` would drop the parentheses of a `raise` that becomes
153
+ # the body in the same pass, which a method passed as an argument needs.
154
+ def self.autocorrect_incompatible_with
155
+ [Style::GuardClause, Style::MethodCallWithArgsParentheses, Style::MethodDefParentheses, Style::RaiseArgs]
156
+ end
157
+
146
158
  def on_def(node)
147
159
  return if node.assignment_method? || use_heredoc?(node)
148
160
 
@@ -213,12 +225,26 @@ module RuboCop
213
225
  return false unless (body = node.body)
214
226
  return true if body.any_str_type? && body.heredoc?
215
227
 
216
- body.each_descendant(:str).any?(&:heredoc?)
228
+ body.each_descendant(:any_str).any?(&:heredoc?)
217
229
  end
218
230
 
219
231
  def correct_to_endless(corrector, node)
220
- corrector.replace(signature_to_body_range(node), ' = ')
221
- corrector.remove(node.body.source_range.end.join(node.loc.end.end))
232
+ if unparenthesized_arguments?(node)
233
+ corrector.replace(name_to_arguments_range(node), '(')
234
+ corrector.replace(signature_to_body_range(node), ') = ')
235
+ else
236
+ corrector.replace(signature_to_body_range(node), ' = ')
237
+ end
238
+
239
+ corrector.remove(body_to_end_range(node))
240
+ end
241
+
242
+ def unparenthesized_arguments?(node)
243
+ node.arguments.any? && !node.arguments.loc.begin
244
+ end
245
+
246
+ def name_to_arguments_range(node)
247
+ node.loc.name.end.join(node.arguments.source_range.begin)
222
248
  end
223
249
 
224
250
  def signature_to_body_range(node)
@@ -227,6 +253,10 @@ module RuboCop
227
253
  signature_end.join(node.body.source_range.begin)
228
254
  end
229
255
 
256
+ def body_to_end_range(node)
257
+ node.body.source_range.end.join(node.loc.end.end)
258
+ end
259
+
230
260
  def endless_replacement(node)
231
261
  <<~RUBY.strip
232
262
  def #{receiver(node)}#{node.method_name}#{arguments(node)} = #{node.body.source}
@@ -238,11 +268,29 @@ module RuboCop
238
268
  end
239
269
 
240
270
  def arguments(node, missing = '')
241
- node.arguments.any? ? node.arguments.source : missing
271
+ return missing unless node.arguments.any?
272
+ return parenthesized_arguments(node) if unparenthesized_arguments?(node)
273
+
274
+ node.arguments.source
275
+ end
276
+
277
+ def parenthesized_arguments(node)
278
+ padding = space_inside_parens? ? ' ' : ''
279
+
280
+ "(#{padding}#{node.arguments.source}#{padding})"
281
+ end
282
+
283
+ def space_inside_parens?
284
+ return false unless config.cop_enabled?('Layout/SpaceInsideParens')
285
+
286
+ config.for_cop('Layout/SpaceInsideParens')['EnforcedStyle'] != 'no_space'
242
287
  end
243
288
 
244
289
  def can_be_made_endless?(node)
245
- node.body && !node.body.type?(:begin, :kwbegin, :rescue, :ensure)
290
+ return false unless node.body
291
+ return false if comment_in_discarded_range?(node)
292
+
293
+ endless_method_body?(node.body)
246
294
  end
247
295
 
248
296
  def single_line_when_made_endless?(node)
@@ -252,7 +300,24 @@ module RuboCop
252
300
  def too_long_when_made_endless?(node)
253
301
  return false unless config.cop_enabled?('Layout/LineLength')
254
302
 
255
- endless_replacement(node).length + node.loc.column > max_line_length
303
+ line_length(line_when_made_endless(node)) > max_line_length
304
+ end
305
+
306
+ def comment_in_discarded_range?(node)
307
+ discarded_ranges = [signature_to_body_range(node), body_to_end_range(node)]
308
+
309
+ processed_source.each_comment_in_lines(node.first_line..node.last_line).any? do |comment|
310
+ discarded_ranges.any? { |range| range.contains?(comment.source_range) }
311
+ end
312
+ end
313
+
314
+ def line_when_made_endless(node)
315
+ keyword_element = node.loc.keyword
316
+ code_before = keyword_element.source_line[0...keyword_element.column]
317
+ end_element = node.loc.end
318
+ code_after = end_element.source_line[end_element.last_column..]
319
+
320
+ "#{code_before}#{endless_replacement(node)}#{code_after}"
256
321
  end
257
322
  end
258
323
  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)