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
@@ -181,11 +181,11 @@ module RuboCop
181
181
  def autocorrect_modifier(corrector, node)
182
182
  body = node.if_branch || node.else_branch
183
183
 
184
- replacement =
185
- "next #{node.inverse_keyword} #{node.condition.source}\n" \
186
- "#{' ' * node.source_range.column}#{body.source}"
184
+ next_code = "next #{node.inverse_keyword} #{node.condition.source}\n" \
185
+ "#{' ' * node.source_range.column}"
187
186
 
188
- corrector.replace(node, replacement)
187
+ corrector.insert_before(node, next_code)
188
+ corrector.remove(range_between(body.source_range.end_pos, node.source_range.end_pos))
189
189
  end
190
190
 
191
191
  def autocorrect_block(corrector, node)
@@ -231,7 +231,7 @@ module RuboCop
231
231
 
232
232
  # end_range starts with the final newline of the if body
233
233
  lines = (node.source_range.line + 1)...node.loc.end.line
234
- lines = lines.to_a - heredoc_lines(node)
234
+ lines = lines.to_a - string_content_lines(node)
235
235
  # Skip blank lines
236
236
  lines.reject { |lineno| /\A\s*\z/.match?(buffer.source_line(lineno)) }
237
237
  end
@@ -250,11 +250,25 @@ module RuboCop
250
250
  lines.map { |lineno| buffer.source_line(lineno) =~ /\S/ }.min
251
251
  end
252
252
 
253
- def heredoc_lines(node)
254
- node.each_node(:dstr)
255
- .select(&:heredoc?)
256
- .map { |n| n.loc.heredoc_body }
257
- .flat_map { |b| (b.line...b.last_line).to_a }
253
+ # Lines that start inside a string literal or a heredoc body, whose
254
+ # leading whitespace is part of the string value.
255
+ def string_content_lines(node)
256
+ buffer = node.source_range.source_buffer
257
+ ranges = node.each_node(:any_str).filter_map { |str| string_content_range(str) }
258
+
259
+ ranges.flat_map do |range|
260
+ (range.line..range.last_line).select do |lineno|
261
+ (range.begin_pos...range.end_pos).cover?(buffer.line_range(lineno).begin_pos)
262
+ end
263
+ end
264
+ end
265
+
266
+ def string_content_range(node)
267
+ if node.heredoc?
268
+ node.loc.heredoc_body
269
+ elsif node.loc?(:begin) && node.loc?(:end)
270
+ node.loc.begin.end.join(node.loc.end.begin)
271
+ end
258
272
  end
259
273
 
260
274
  def reindent_line(corrector, lineno, delta, buffer)
@@ -44,6 +44,7 @@ module RuboCop
44
44
 
45
45
  def on_send(node)
46
46
  return unless node.receiver
47
+ return if prefer_comparison? && comment_before_dot?(node)
47
48
 
48
49
  style_check?(node) do
49
50
  add_offense(node.loc.selector) do |corrector|
@@ -58,12 +59,19 @@ module RuboCop
58
59
 
59
60
  private
60
61
 
62
+ def comment_before_dot?(node)
63
+ return false unless node.loc.dot
64
+
65
+ processed_source.contains_comment?(node.receiver.source_range.end.join(node.loc.dot))
66
+ end
67
+
61
68
  def autocorrect_to_comparison(corrector, node)
62
- range = node.loc.dot.join(node.loc.selector.end)
69
+ range = node.receiver.source_range.end.join(node.loc.selector.end)
63
70
  corrector.replace(range, ' == nil')
64
- # The new `== nil` binds looser than an enclosing operator (e.g. `<<`,
65
- # `!`), so wrap it to keep the original precedence.
66
- corrector.wrap(node, '(', ')') if operator_expression?(node.parent)
71
+
72
+ return if !operator_expression?(node.parent) && !receiver_of_parent?(node)
73
+
74
+ corrector.wrap(node, '(', ')')
67
75
  end
68
76
 
69
77
  def autocorrect_to_predicate(corrector, node)
@@ -88,6 +96,10 @@ module RuboCop
88
96
  node.assignment?
89
97
  end
90
98
 
99
+ def receiver_of_parent?(node)
100
+ node.parent&.call_type? && node.parent.receiver.equal?(node)
101
+ end
102
+
91
103
  def operator_send?(node)
92
104
  node.send_type? && node.operator_method? && !node.method?(:[]) && !node.method?(:[]=)
93
105
  end
@@ -130,7 +130,7 @@ module RuboCop
130
130
 
131
131
  def operator_expression?(node)
132
132
  node.operator_keyword? ||
133
- (node.send_type? && node.binary_operation?) ||
133
+ (node.send_type? && node.operator_method?) ||
134
134
  (node.if_type? && node.ternary?) ||
135
135
  node.type?(:range, :iflipflop, :eflipflop) ||
136
136
  node.assignment?
@@ -54,11 +54,10 @@ module RuboCop
54
54
  end
55
55
 
56
56
  def requires_parens?(child)
57
- child.operator_keyword? ||
58
- (child.send_type? && child.binary_operation?) ||
59
- (child.if_type? && child.ternary?) ||
60
- child.type?(:range, :iflipflop, :eflipflop) ||
61
- child.assignment?
57
+ return true if child.operator_keyword? || operator_method_requiring_parens?(child)
58
+ return true if child.if_type? && child.ternary?
59
+
60
+ child.type?(:range, :iflipflop, :eflipflop) || child.assignment?
62
61
  end
63
62
 
64
63
  def correct_opposite_method(corrector, range, child)
@@ -74,6 +73,12 @@ module RuboCop
74
73
  def correct_without_parens(corrector, range)
75
74
  corrector.replace(range, '!')
76
75
  end
76
+
77
+ def operator_method_requiring_parens?(child)
78
+ return false unless child.send_type?
79
+
80
+ child.binary_operation? || (child.unary_operation? && child.method?(:~))
81
+ end
77
82
  end
78
83
  end
79
84
  end
@@ -90,6 +90,7 @@ module RuboCop
90
90
  def on_send(node)
91
91
  numeric, replacement = check(node)
92
92
  return unless numeric
93
+ return if handled_by_bitwise_predicate?(node)
93
94
 
94
95
  return if allowed_method_name?(node.method_name) ||
95
96
  node.each_ancestor(:send, :any_block).any? do |ancestor|
@@ -108,6 +109,12 @@ module RuboCop
108
109
  allowed_method?(name) || matches_allowed_pattern?(name)
109
110
  end
110
111
 
112
+ def handled_by_bitwise_predicate?(node)
113
+ bitwise_predicate_candidate?(node) &&
114
+ config.for_cop('Style/BitwisePredicate').fetch('Enabled') &&
115
+ BitwisePredicate.support_target_ruby_version?(target_ruby_version)
116
+ end
117
+
111
118
  def check(node)
112
119
  numeric, operator =
113
120
  if style == :predicate
@@ -124,7 +131,7 @@ module RuboCop
124
131
  def replacement(node, numeric, operation)
125
132
  if style == :predicate
126
133
  [parenthesized_source(numeric), REPLACEMENTS.invert[operation.to_s]].join('.')
127
- elsif negated?(node)
134
+ elsif comparison_requires_parentheses?(node)
128
135
  "(#{numeric.source} #{REPLACEMENTS[operation.to_s]} 0)"
129
136
  else
130
137
  [numeric.source, REPLACEMENTS[operation.to_s], 0].join(' ')
@@ -140,7 +147,7 @@ module RuboCop
140
147
  end
141
148
 
142
149
  def require_parentheses?(node)
143
- node.send_type? && node.binary_operation? && !node.parenthesized?
150
+ node.send_type? && node.operator_method? && !node.parenthesized?
144
151
  end
145
152
 
146
153
  def replacement_supported?(operator)
@@ -159,10 +166,11 @@ module RuboCop
159
166
  end
160
167
  end
161
168
 
162
- def negated?(node)
163
- return false unless (parent = node.parent)
169
+ def comparison_requires_parentheses?(node)
170
+ return false unless (parent = node.parent)&.call_type?
171
+ return true if parent.receiver.equal?(node)
164
172
 
165
- parent.send_type? && parent.method?(:!)
173
+ parent.operator_method? && !parent.method?(:[]) && !parent.method?(:[]=)
166
174
  end
167
175
 
168
176
  # @!method predicate(node)
@@ -179,6 +187,14 @@ module RuboCop
179
187
  def_node_matcher :inverted_comparison, <<~PATTERN
180
188
  (send (int 0) ${:== :> :<} [$(...) !gvar_type?])
181
189
  PATTERN
190
+
191
+ # @!method bitwise_predicate_candidate?(node)
192
+ def_node_matcher :bitwise_predicate_candidate?, <<~PATTERN
193
+ {
194
+ (send {(begin (send _ :& _)) (send _ :& _)} {:positive? :zero?})
195
+ (send {(begin (send _ :& _)) (send _ :& _)} {:> :==} (int 0))
196
+ }
197
+ PATTERN
182
198
  end
183
199
  end
184
200
  end
@@ -102,6 +102,7 @@ module RuboCop
102
102
  return replaced_node unless node.parent
103
103
  return "(#{replaced_node})" if node.parent.operator_keyword?
104
104
  return "(#{replaced_node})" if node.parent.send_type? && node.parent.operator_method?
105
+ return "(#{replaced_node})" if parent_binds_tighter_than_ternary?(node)
105
106
 
106
107
  replaced_node
107
108
  end
@@ -124,6 +125,14 @@ module RuboCop
124
125
  "#{expr_replacement(else_branch)}"
125
126
  end
126
127
 
128
+ def parent_binds_tighter_than_ternary?(node)
129
+ parent = node.parent
130
+ return parent.receiver.equal?(node) if parent.call_type?
131
+ return parent.namespace.equal?(node) if parent.type?(:const, :casgn)
132
+
133
+ parent.if_type? && parent.ternary? && parent.condition.equal?(node)
134
+ end
135
+
127
136
  def expr_replacement(node)
128
137
  return 'nil' if node.nil?
129
138
 
@@ -131,7 +140,9 @@ module RuboCop
131
140
  end
132
141
 
133
142
  def requires_parentheses?(node)
134
- return true if %i[and or if].include?(node.type)
143
+ return true if node.type?(
144
+ :and, :or, :if, :rescue, :while, :until, :while_post, :until_post, :any_match_pattern
145
+ )
135
146
  return true if node.assignment?
136
147
  return true if method_call_with_changed_precedence?(node)
137
148
 
@@ -139,7 +150,7 @@ module RuboCop
139
150
  end
140
151
 
141
152
  def method_call_with_changed_precedence?(node)
142
- return false unless node.send_type? && node.arguments?
153
+ return false unless node.call_type? && node.arguments?
143
154
  return false if node.parenthesized_call?
144
155
 
145
156
  !node.operator_method?
@@ -83,7 +83,7 @@ module RuboCop
83
83
  control_op_condition(cond) do |first_child, rest_children|
84
84
  return if require_parentheses?(node, first_child)
85
85
  return if semicolon_separated_expressions?(first_child, rest_children)
86
- return if modifier_op?(first_child)
86
+ return if modifier_op?(first_child) || first_child.masgn_type?
87
87
  return if parens_allowed?(cond)
88
88
 
89
89
  message = message(cond)
@@ -86,10 +86,21 @@ module RuboCop
86
86
  end
87
87
 
88
88
  def include_same_character_as_used_for_delimiter?(node, type)
89
- return false unless %w[%w %i].include?(type)
90
-
91
89
  used_delimiters = matchpairs(begin_source(node)[-1])
92
- contains_delimiter?(node, used_delimiters)
90
+ return contains_delimiter?(node, used_delimiters) if %w[%w %i].include?(type)
91
+ return false unless %w[%q %s %r].include?(type)
92
+
93
+ contains_escaped_delimiter?(node, used_delimiters)
94
+ end
95
+
96
+ # Without interpolation an escaped delimiter means something different
97
+ # once the delimiters change (`%q!it\!s!` is `"it!s"`, but `%q(it\!s)`
98
+ # is `"it\\!s"`). For a regexp the match stays the same, but its source
99
+ # changes. Interpolating literals read `\!` as `!` with any delimiter.
100
+ def contains_escaped_delimiter?(node, delimiters)
101
+ body = node.loc.begin.end.join(node.loc.end.begin).source
102
+
103
+ body.scan(/\\./m).any? { |escape| delimiters.include?(escape[1]) }
93
104
  end
94
105
 
95
106
  def contains_delimiter?(node, delimiters)
@@ -101,10 +101,7 @@ module RuboCop
101
101
  return if exception.send_type? && exception.first_argument&.hash_type?
102
102
 
103
103
  add_offense(node, message: format(COMPACT_MSG, method: node.method_name)) do |corrector|
104
- replacement = correction_exploded_to_compact(node)
105
-
106
- corrector.replace(node, replacement)
107
- opposite_style_detected
104
+ replace_raise(corrector, node, correction_exploded_to_compact(node))
108
105
  end
109
106
  else
110
107
  correct_style_detected
@@ -121,13 +118,22 @@ module RuboCop
121
118
  return if allowed_non_exploded_type?(first_arg)
122
119
 
123
120
  add_offense(node, message: format(EXPLODED_MSG, method: node.method_name)) do |corrector|
124
- replacement = correction_compact_to_exploded(node)
125
-
126
- corrector.replace(node, replacement)
127
- opposite_style_detected
121
+ replace_raise(corrector, node, correction_compact_to_exploded(node))
128
122
  end
129
123
  end
130
124
 
125
+ def replace_raise(corrector, node, replacement)
126
+ opposite_style_detected
127
+ return if inside_corrected_raise?(node)
128
+
129
+ corrector.replace(node, replacement)
130
+ ignore_node(node)
131
+ end
132
+
133
+ def inside_corrected_raise?(node)
134
+ node.each_ancestor(:send).any? { |ancestor| ignored_node?(ancestor) }
135
+ end
136
+
131
137
  def use_new_method?(first_arg)
132
138
  first_arg.send_type? && first_arg.receiver && first_arg.method?(:new)
133
139
  end
@@ -152,7 +158,11 @@ module RuboCop
152
158
  end
153
159
 
154
160
  def requires_parens?(parent)
155
- parent.operator_keyword? || (parent.if_type? && parent.ternary?)
161
+ return true if parent.operator_keyword?
162
+ return true if parent.type?(:call, :super, :yield, :array, :pair)
163
+ return true if parent.any_def_type? && parent.endless?
164
+
165
+ parent.if_type? && parent.ternary?
156
166
  end
157
167
  end
158
168
  end
@@ -183,7 +183,7 @@ module RuboCop
183
183
  end
184
184
 
185
185
  def adjusted_source(expr_node, block_node)
186
- source = expr_node.source
186
+ source = element_source(expr_node)
187
187
  return source unless block_node.numblock_type?
188
188
  return source if block_node.method?(:each_with_object)
189
189
 
@@ -194,6 +194,22 @@ module RuboCop
194
194
  def indent(node)
195
195
  ' ' * node.source_range.column
196
196
  end
197
+
198
+ def element_source(expr_node)
199
+ if command_call?(expr_node) || expr_node.assignment? || expr_node.rescue_type?
200
+ return "(#{expr_node.source})"
201
+ end
202
+ return "[#{expr_node.source}]" if expr_node.array_type? && !expr_node.bracketed?
203
+
204
+ expr_node.source
205
+ end
206
+
207
+ def command_call?(node)
208
+ node = node.send_node if node.any_block_type?
209
+ return false unless node.type?(:call, :super, :yield)
210
+
211
+ node.arguments? && !node.parenthesized? && !node.operator_method? && !node.setter_method?
212
+ end
197
213
  end
198
214
  end
199
215
  end
@@ -139,7 +139,7 @@ module RuboCop
139
139
  offense_range = node.loc.begin
140
140
 
141
141
  add_offense(offense_range) do |corrector|
142
- if node.parent&.assignment?
142
+ if node.parent&.assignment? || node.parent&.type?(:return, :next, :break)
143
143
  replace_begin_with_statement(corrector, offense_range, node)
144
144
  else
145
145
  remove_begin(corrector, offense_range, node)
@@ -156,7 +156,7 @@ module RuboCop
156
156
  first_child = node.children.first
157
157
 
158
158
  source = first_child.source
159
- source = "(#{source})" if first_child.if_type? && first_child.modifier_form?
159
+ source = "(#{source})" if low_precedence_statement?(first_child)
160
160
 
161
161
  corrector.replace(offense_range, source)
162
162
  corrector.remove(range_between(offense_range.end_pos, first_child.source_range.end_pos))
@@ -164,6 +164,13 @@ module RuboCop
164
164
  restore_removed_comments(corrector, offense_range, node, first_child)
165
165
  end
166
166
 
167
+ def low_precedence_statement?(node)
168
+ return node.modifier_form? if node.basic_conditional?
169
+ return node.prefix_not? if node.send_type?
170
+
171
+ node.operator_keyword? && node.semantic_operator?
172
+ end
173
+
167
174
  def remove_begin(corrector, offense_range, node)
168
175
  if node.parent.respond_to?(:endless?) && node.parent.endless?
169
176
  offense_range = range_with_surrounding_space(offense_range, newlines: true)
@@ -184,7 +191,8 @@ module RuboCop
184
191
  def use_modifier_form_after_multiline_begin_block?(node)
185
192
  return false unless (parent = node.parent)
186
193
 
187
- node.multiline? && parent.if_type? && parent.modifier_form?
194
+ node.multiline? && parent.if_type? && parent.modifier_form? &&
195
+ !parent.condition.equal?(node)
188
196
  end
189
197
 
190
198
  def correct_modifier_form_after_multiline_begin_block(corrector, node)
@@ -222,7 +230,17 @@ module RuboCop
222
230
  end
223
231
 
224
232
  def valid_begin_assignment?(node)
225
- node.parent&.assignment? && !node.children.one?
233
+ node.parent&.assignment? && (!node.children.one? || too_long_when_joined?(node))
234
+ end
235
+
236
+ def too_long_when_joined?(node)
237
+ statement = node.children.first
238
+ max = config.for_enabled_cop('Layout/LineLength')['Max']
239
+ return false unless max && statement.single_line?
240
+
241
+ length = statement.source.length
242
+ length += 2 if low_precedence_statement?(statement)
243
+ node.loc.column + length > max
226
244
  end
227
245
 
228
246
  def inspect_branches(node)
@@ -78,7 +78,10 @@ module RuboCop
78
78
  ].freeze
79
79
 
80
80
  def on_if(node)
81
- return if node.modifier_form? || node.elsif_conditional? || !offense?(node)
81
+ return unless redundant_condition_offense?(node)
82
+ return if node.each_ancestor(:if).any? do |ancestor|
83
+ redundant_condition_offense?(ancestor)
84
+ end
82
85
 
83
86
  message = message(node)
84
87
 
@@ -89,6 +92,10 @@ module RuboCop
89
92
 
90
93
  private
91
94
 
95
+ def redundant_condition_offense?(node)
96
+ !node.modifier_form? && !node.elsif_conditional? && offense?(node)
97
+ end
98
+
92
99
  def message(node)
93
100
  if redundant_condition?(node)
94
101
  REDUNDANT_CONDITION
@@ -240,10 +247,10 @@ module RuboCop
240
247
  end
241
248
 
242
249
  # rubocop:disable-next Metrics/AbcSize
243
- def if_source(if_branch, arithmetic_operation)
250
+ def if_source(if_branch, binary_operator)
244
251
  if branches_have_method?(if_branch.parent) && if_branch.parenthesized?
245
252
  if_branch.source.delete_suffix(')')
246
- elsif arithmetic_operation
253
+ elsif binary_operator
247
254
  argument_source = if_branch.first_argument.source
248
255
 
249
256
  "#{if_branch.receiver.source} #{if_branch.method_name} (#{argument_source}"
@@ -257,8 +264,8 @@ module RuboCop
257
264
  end
258
265
  end
259
266
 
260
- def else_source(else_branch, arithmetic_operation) # rubocop:disable Metrics/AbcSize
261
- if arithmetic_operation
267
+ def else_source(else_branch, binary_operator) # rubocop:disable Metrics/AbcSize
268
+ if binary_operator
262
269
  "#{else_branch.first_argument.source})"
263
270
  elsif branches_have_method?(else_branch.parent)
264
271
  else_source_if_has_method(else_branch)
@@ -295,11 +302,11 @@ module RuboCop
295
302
 
296
303
  def make_ternary_form(node)
297
304
  _condition, if_branch, else_branch = *node # rubocop:disable InternalAffairs/NodeDestructuring -- takes all three branches in one step
298
- arithmetic_operation = use_arithmetic_operation?(if_branch)
305
+ binary_operator = branches_have_method?(node) && binary_operator?(if_branch)
299
306
 
300
307
  ternary_form = [
301
- if_source(if_branch, arithmetic_operation),
302
- else_source(else_branch, arithmetic_operation)
308
+ if_source(if_branch, binary_operator),
309
+ else_source(else_branch, binary_operator)
303
310
  ].join(' || ')
304
311
  ternary_form += ')' if branches_have_method?(node) && if_branch.parenthesized?
305
312
 
@@ -320,17 +327,27 @@ module RuboCop
320
327
 
321
328
  def require_parentheses?(node)
322
329
  (node.basic_conditional? && node.modifier_form?) ||
323
- node.range_type? ||
324
- node.rescue_type? ||
325
- (node.respond_to?(:semantic_operator?) && node.semantic_operator?)
330
+ node.range_type? || node.rescue_type? ||
331
+ (node.respond_to?(:semantic_operator?) && node.semantic_operator?) ||
332
+ low_precedence_keyword?(node)
333
+ end
334
+
335
+ # e.g. `not foo`, `return foo`, and `yield foo`
336
+ def low_precedence_keyword?(node)
337
+ return node.prefix_not? if node.send_type?
338
+
339
+ node.type?(:return, :next, :break, :yield, :super) &&
340
+ node.arguments? && !node.parenthesized?
326
341
  end
327
342
 
328
343
  def require_braces?(node)
329
344
  node.hash_type? && !node.braces?
330
345
  end
331
346
 
332
- def use_arithmetic_operation?(node)
333
- node.respond_to?(:arithmetic_operation?) && node.arithmetic_operation?
347
+ # Any binary operator binds tighter than `||`, so the fallback must be
348
+ # parenthesized as its argument, e.g. `stack << (foo || :new)`.
349
+ def binary_operator?(node)
350
+ node.binary_operation? && !node.dot?
334
351
  end
335
352
 
336
353
  def without_argument_parentheses_method?(node)
@@ -25,6 +25,12 @@ module RuboCop
25
25
  MSG = 'Remove the redundant double splat and braces, use keyword arguments directly.'
26
26
  MERGE_METHODS = %i[merge merge!].freeze
27
27
 
28
+ # `Style/TrailingCommaInHashLiteral` adds a comma to the hash literal whose braces
29
+ # this cop removes in the same pass, which would leave a dangling comma.
30
+ def self.autocorrect_incompatible_with
31
+ [Style::TrailingCommaInHashLiteral]
32
+ end
33
+
28
34
  # rubocop:disable-next Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
29
35
  def on_hash(node)
30
36
  return if node.pairs.empty? || node.pairs.any?(&:hash_rocket?)
@@ -49,7 +49,7 @@ module RuboCop
49
49
  end
50
50
 
51
51
  def replaced_exploded(node, command, message)
52
- arg = string_message?(message) ? message.source : "#{message.source}.to_s"
52
+ arg = message_source(message)
53
53
  arg = node.parenthesized? ? "(#{arg})" : " #{arg}"
54
54
  "#{command}#{arg}"
55
55
  end
@@ -63,19 +63,31 @@ module RuboCop
63
63
  next if message.nil_type?
64
64
 
65
65
  add_offense(node, message: MSG_2) do |corrector|
66
- corrector.replace(new_call, replaced_compact(message))
66
+ corrector.replace(new_call, message_source(message))
67
67
  end
68
68
  end
69
69
  end
70
70
 
71
- def replaced_compact(message)
71
+ def message_source(message)
72
72
  if string_message?(message)
73
73
  message.source
74
+ elsif require_parentheses?(message)
75
+ "(#{message.source}).to_s"
74
76
  else
75
77
  "#{message.source}.to_s"
76
78
  end
77
79
  end
78
80
 
81
+ def require_parentheses?(message)
82
+ if message.call_type?
83
+ !message.method?(:[]) &&
84
+ (message.operator_method? || (message.arguments? && !message.parenthesized?))
85
+ else
86
+ !(message.basic_literal? || message.variable? ||
87
+ message.type?(:const, :self, :begin, :array))
88
+ end
89
+ end
90
+
79
91
  # @!method exploded?(node)
80
92
  def_node_matcher :exploded?, <<~PATTERN
81
93
  (send nil? ${:raise :fail} (const {nil? cbase} :RuntimeError) $_)
@@ -74,7 +74,8 @@ module RuboCop
74
74
  def should_not_check?(send, body)
75
75
  (body&.const_type? && !check_for_constant?) ||
76
76
  (body&.str_type? && !check_for_string?) ||
77
- rails_cache?(send.receiver)
77
+ rails_cache?(send.receiver) ||
78
+ unappendable_argument?(send)
78
79
  end
79
80
 
80
81
  # @!method rails_cache?(node)
@@ -107,6 +108,13 @@ module RuboCop
107
108
  def check_for_string?
108
109
  frozen_string_literals_enabled?
109
110
  end
111
+
112
+ def unappendable_argument?(send)
113
+ argument = send.first_argument
114
+
115
+ argument.type?(:splat, :forwarded_restarg) ||
116
+ (argument.hash_type? && !argument.braces?)
117
+ end
110
118
  end
111
119
  end
112
120
  end
@@ -29,8 +29,7 @@ module RuboCop
29
29
  operation_produces_immutable_object?(node.receiver))
30
30
 
31
31
  add_offense(node) do |corrector|
32
- corrector.remove(node.loc.dot)
33
- corrector.remove(node.loc.selector)
32
+ corrector.remove(node.loc.dot.join(node.loc.selector))
34
33
  end
35
34
  end
36
35
 
@@ -57,12 +56,21 @@ module RuboCop
57
56
  def_node_matcher :operation_produces_immutable_object?, <<~PATTERN
58
57
  {
59
58
  (begin (send {float int} {:+ :- :* :** :/ :% :<<} _))
60
- (begin (send !{(str _) array} {:+ :- :* :** :/ :%} {float int}))
59
+ (begin (send #numeric_operation? {:+ :- :* :** :/ :%} {float int}))
61
60
  (begin (send _ {:== :=== :!= :<= :>= :< :>} _))
62
61
  (send _ {:count :length :size} ...)
63
62
  (any_block (send _ {:count :length :size} ...) ...)
64
63
  }
65
64
  PATTERN
65
+
66
+ # @!method numeric_operation?(node)
67
+ def_node_matcher :numeric_operation?, <<~PATTERN
68
+ {
69
+ float int
70
+ (begin #numeric_operation?)
71
+ (send #numeric_operation? {:+ :- :* :** :/ :%} #numeric_operation?)
72
+ }
73
+ PATTERN
66
74
  end
67
75
  end
68
76
  end