rubocop 1.91.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 (236) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  3. data/config/default.yml +3 -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/base.rb +42 -21
  19. data/lib/rubocop/cop/bundler/gem_comment.rb +0 -1
  20. data/lib/rubocop/cop/bundler/ordered_gems.rb +0 -8
  21. data/lib/rubocop/cop/cop_documentation.rb +81 -0
  22. data/lib/rubocop/cop/cop_explanation.rb +140 -0
  23. data/lib/rubocop/cop/correctors/alignment_corrector.rb +13 -1
  24. data/lib/rubocop/cop/correctors/each_to_for_corrector.rb +22 -7
  25. data/lib/rubocop/cop/correctors/empty_line_corrector.rb +1 -1
  26. data/lib/rubocop/cop/correctors/lambda_literal_to_method_corrector.rb +23 -2
  27. data/lib/rubocop/cop/correctors/parentheses_corrector.rb +45 -8
  28. data/lib/rubocop/cop/documentation.rb +1 -1
  29. data/lib/rubocop/cop/gemspec/dependency_version.rb +0 -6
  30. data/lib/rubocop/cop/gemspec/ordered_dependencies.rb +0 -6
  31. data/lib/rubocop/cop/internal_affairs/node_matcher_directive.rb +2 -5
  32. data/lib/rubocop/cop/internal_affairs/node_type_group.rb +0 -7
  33. data/lib/rubocop/cop/internal_affairs/redundant_context_config_parameter.rb +1 -7
  34. data/lib/rubocop/cop/internal_affairs/style_detected_api_use.rb +1 -7
  35. data/lib/rubocop/cop/layout/block_alignment.rb +27 -0
  36. data/lib/rubocop/cop/layout/case_indentation.rb +25 -9
  37. data/lib/rubocop/cop/layout/class_structure.rb +9 -1
  38. data/lib/rubocop/cop/layout/def_end_alignment.rb +4 -0
  39. data/lib/rubocop/cop/layout/empty_line_after_multiline_condition.rb +14 -5
  40. data/lib/rubocop/cop/layout/empty_lines.rb +5 -2
  41. data/lib/rubocop/cop/layout/empty_lines_around_attribute_accessor.rb +1 -0
  42. data/lib/rubocop/cop/layout/empty_lines_around_block_body.rb +1 -1
  43. data/lib/rubocop/cop/layout/empty_lines_around_exception_handling_keywords.rb +23 -1
  44. data/lib/rubocop/cop/layout/end_alignment.rb +16 -10
  45. data/lib/rubocop/cop/layout/extra_spacing.rb +4 -0
  46. data/lib/rubocop/cop/layout/first_argument_indentation.rb +1 -0
  47. data/lib/rubocop/cop/layout/first_hash_element_indentation.rb +5 -3
  48. data/lib/rubocop/cop/layout/first_method_argument_line_break.rb +6 -0
  49. data/lib/rubocop/cop/layout/first_method_parameter_line_break.rb +4 -0
  50. data/lib/rubocop/cop/layout/hash_alignment.rb +16 -12
  51. data/lib/rubocop/cop/layout/heredoc_argument_closing_parenthesis.rb +2 -0
  52. data/lib/rubocop/cop/layout/heredoc_indentation.rb +15 -0
  53. data/lib/rubocop/cop/layout/indentation_style.rb +4 -1
  54. data/lib/rubocop/cop/layout/indentation_width.rb +2 -2
  55. data/lib/rubocop/cop/layout/line_continuation_spacing.rb +8 -1
  56. data/lib/rubocop/cop/layout/line_end_string_concatenation_indentation.rb +25 -9
  57. data/lib/rubocop/cop/layout/line_length.rb +52 -18
  58. data/lib/rubocop/cop/layout/multiline_array_brace_layout.rb +12 -0
  59. data/lib/rubocop/cop/layout/multiline_block_layout.rb +8 -1
  60. data/lib/rubocop/cop/layout/multiline_hash_brace_layout.rb +7 -0
  61. data/lib/rubocop/cop/layout/multiline_method_call_brace_layout.rb +6 -0
  62. data/lib/rubocop/cop/layout/multiline_method_call_indentation.rb +14 -0
  63. data/lib/rubocop/cop/layout/multiline_method_definition_brace_layout.rb +4 -0
  64. data/lib/rubocop/cop/layout/redundant_line_break.rb +22 -1
  65. data/lib/rubocop/cop/layout/rescue_ensure_alignment.rb +15 -2
  66. data/lib/rubocop/cop/layout/single_line_block_chain.rb +10 -2
  67. data/lib/rubocop/cop/layout/space_after_not.rb +5 -0
  68. data/lib/rubocop/cop/layout/space_around_block_parameters.rb +6 -11
  69. data/lib/rubocop/cop/layout/space_around_keyword.rb +10 -0
  70. data/lib/rubocop/cop/layout/space_before_block_braces.rb +4 -1
  71. data/lib/rubocop/cop/layout/space_inside_array_literal_brackets.rb +15 -2
  72. data/lib/rubocop/cop/layout/space_inside_block_braces.rb +31 -8
  73. data/lib/rubocop/cop/lint/ambiguous_operator.rb +1 -1
  74. data/lib/rubocop/cop/lint/ambiguous_operator_precedence.rb +2 -1
  75. data/lib/rubocop/cop/lint/empty_conditional_body.rb +3 -1
  76. data/lib/rubocop/cop/lint/literal_as_condition.rb +15 -0
  77. data/lib/rubocop/cop/lint/literal_in_interpolation.rb +63 -19
  78. data/lib/rubocop/cop/lint/loop.rb +24 -2
  79. data/lib/rubocop/cop/lint/misplaced_magic_comment.rb +71 -11
  80. data/lib/rubocop/cop/lint/missing_cop_enable_directive.rb +18 -3
  81. data/lib/rubocop/cop/lint/number_conversion.rb +1 -1
  82. data/lib/rubocop/cop/lint/redundant_cop_disable_directive.rb +12 -1
  83. data/lib/rubocop/cop/lint/redundant_dir_glob_sort.rb +13 -2
  84. data/lib/rubocop/cop/lint/unescaped_bracket_in_regexp.rb +18 -1
  85. data/lib/rubocop/cop/lint/useless_assignment.rb +14 -7
  86. data/lib/rubocop/cop/lint/void.rb +8 -1
  87. data/lib/rubocop/cop/metrics/class_length.rb +4 -2
  88. data/lib/rubocop/cop/metrics/cyclomatic_complexity.rb +0 -4
  89. data/lib/rubocop/cop/metrics/module_length.rb +4 -2
  90. data/lib/rubocop/cop/mixin/alignment.rb +4 -0
  91. data/lib/rubocop/cop/mixin/check_line_breakable.rb +17 -14
  92. data/lib/rubocop/cop/mixin/check_single_line_suitability.rb +5 -1
  93. data/lib/rubocop/cop/mixin/code_length.rb +10 -2
  94. data/lib/rubocop/cop/mixin/empty_lines_around_body.rb +12 -1
  95. data/lib/rubocop/cop/mixin/endless_method_body_help.rb +69 -0
  96. data/lib/rubocop/cop/mixin/forbidden_identifiers.rb +2 -2
  97. data/lib/rubocop/cop/mixin/hash_alignment_styles.rb +15 -1
  98. data/lib/rubocop/cop/mixin/hash_shorthand_syntax.rb +26 -2
  99. data/lib/rubocop/cop/mixin/min_branches_count.rb +3 -1
  100. data/lib/rubocop/cop/mixin/multiline_element_line_breaks.rb +11 -1
  101. data/lib/rubocop/cop/mixin/multiline_literal_brace_layout.rb +9 -0
  102. data/lib/rubocop/cop/mixin/ordered_gem_node.rb +1 -1
  103. data/lib/rubocop/cop/mixin/pattern_matching_help.rb +35 -0
  104. data/lib/rubocop/cop/mixin/preceding_following_alignment.rb +17 -2
  105. data/lib/rubocop/cop/mixin/project_index_help.rb +15 -0
  106. data/lib/rubocop/cop/mixin/reparsed_equivalence.rb +13 -3
  107. data/lib/rubocop/cop/mixin/statement_modifier.rb +22 -2
  108. data/lib/rubocop/cop/mixin.rb +2 -0
  109. data/lib/rubocop/cop/naming/accessor_method_name.rb +0 -16
  110. data/lib/rubocop/cop/naming/block_forwarding.rb +1 -1
  111. data/lib/rubocop/cop/naming/heredoc_delimiter_case.rb +9 -2
  112. data/lib/rubocop/cop/naming/heredoc_delimiter_naming.rb +5 -3
  113. data/lib/rubocop/cop/naming/predicate_prefix.rb +7 -22
  114. data/lib/rubocop/cop/offense.rb +59 -3
  115. data/lib/rubocop/cop/registry.rb +1 -1
  116. data/lib/rubocop/cop/security/json_load.rb +16 -4
  117. data/lib/rubocop/cop/style/access_modifier_declarations.rb +7 -0
  118. data/lib/rubocop/cop/style/alias.rb +18 -5
  119. data/lib/rubocop/cop/style/ambiguous_endless_method_definition.rb +4 -3
  120. data/lib/rubocop/cop/style/and_or.rb +4 -0
  121. data/lib/rubocop/cop/style/arguments_forwarding.rb +4 -1
  122. data/lib/rubocop/cop/style/attr.rb +3 -4
  123. data/lib/rubocop/cop/style/bitwise_predicate.rb +6 -4
  124. data/lib/rubocop/cop/style/block_delimiters.rb +29 -3
  125. data/lib/rubocop/cop/style/case_like_if.rb +6 -3
  126. data/lib/rubocop/cop/style/class_and_module_children.rb +54 -16
  127. data/lib/rubocop/cop/style/class_methods_definitions.rb +8 -0
  128. data/lib/rubocop/cop/style/combinable_loops.rb +85 -11
  129. data/lib/rubocop/cop/style/concat_array_literals.rb +46 -7
  130. data/lib/rubocop/cop/style/conditional_assignment.rb +4 -0
  131. data/lib/rubocop/cop/style/disable_cops_within_source_code_directive.rb +65 -9
  132. data/lib/rubocop/cop/style/double_negation.rb +11 -0
  133. data/lib/rubocop/cop/style/empty_literal.rb +1 -1
  134. data/lib/rubocop/cop/style/empty_method.rb +3 -1
  135. data/lib/rubocop/cop/style/endless_method.rb +67 -6
  136. data/lib/rubocop/cop/style/exact_regexp_match.rb +45 -8
  137. data/lib/rubocop/cop/style/explicit_block_argument.rb +50 -10
  138. data/lib/rubocop/cop/style/float_division.rb +22 -2
  139. data/lib/rubocop/cop/style/for.rb +40 -2
  140. data/lib/rubocop/cop/style/format_string.rb +28 -6
  141. data/lib/rubocop/cop/style/frozen_string_literal_comment.rb +2 -1
  142. data/lib/rubocop/cop/style/guard_clause.rb +9 -1
  143. data/lib/rubocop/cop/style/hash_conversion.rb +28 -8
  144. data/lib/rubocop/cop/style/hash_lookup_method.rb +32 -9
  145. data/lib/rubocop/cop/style/hash_syntax.rb +16 -3
  146. data/lib/rubocop/cop/style/identical_conditional_branches.rb +22 -3
  147. data/lib/rubocop/cop/style/if_inside_else.rb +21 -1
  148. data/lib/rubocop/cop/style/if_unless_modifier.rb +19 -20
  149. data/lib/rubocop/cop/style/if_unless_modifier_of_if_unless.rb +4 -0
  150. data/lib/rubocop/cop/style/if_with_semicolon.rb +47 -4
  151. data/lib/rubocop/cop/style/inverse_methods.rb +24 -2
  152. data/lib/rubocop/cop/style/it_block_parameter.rb +32 -3
  153. data/lib/rubocop/cop/style/keyword_parameters_order.rb +3 -1
  154. data/lib/rubocop/cop/style/lambda.rb +18 -0
  155. data/lib/rubocop/cop/style/lambda_call.rb +7 -2
  156. data/lib/rubocop/cop/style/map_into_array.rb +1 -2
  157. data/lib/rubocop/cop/style/method_call_with_args_parentheses/omit_parentheses.rb +45 -10
  158. data/lib/rubocop/cop/style/method_call_with_args_parentheses.rb +25 -2
  159. data/lib/rubocop/cop/style/method_call_without_args_parentheses.rb +26 -3
  160. data/lib/rubocop/cop/style/method_def_parentheses.rb +29 -3
  161. data/lib/rubocop/cop/style/missing_else.rb +7 -1
  162. data/lib/rubocop/cop/style/multiline_if_modifier.rb +45 -5
  163. data/lib/rubocop/cop/style/multiline_in_pattern_then.rb +7 -0
  164. data/lib/rubocop/cop/style/multiline_method_signature.rb +2 -5
  165. data/lib/rubocop/cop/style/multiline_ternary_operator.rb +7 -3
  166. data/lib/rubocop/cop/style/multiline_when_then.rb +7 -0
  167. data/lib/rubocop/cop/style/mutable_constant.rb +28 -1
  168. data/lib/rubocop/cop/style/negated_if_else_condition.rb +2 -1
  169. data/lib/rubocop/cop/style/negative_array_index.rb +9 -1
  170. data/lib/rubocop/cop/style/nested_modifier.rb +27 -8
  171. data/lib/rubocop/cop/style/nested_ternary_operator.rb +26 -8
  172. data/lib/rubocop/cop/style/next.rb +24 -10
  173. data/lib/rubocop/cop/style/nil_comparison.rb +16 -4
  174. data/lib/rubocop/cop/style/non_nil_check.rb +1 -1
  175. data/lib/rubocop/cop/style/not.rb +10 -5
  176. data/lib/rubocop/cop/style/numeric_predicate.rb +21 -5
  177. data/lib/rubocop/cop/style/one_line_conditional.rb +13 -2
  178. data/lib/rubocop/cop/style/percent_literal_delimiters.rb +14 -3
  179. data/lib/rubocop/cop/style/reduce_to_hash.rb +17 -1
  180. data/lib/rubocop/cop/style/redundant_begin.rb +20 -3
  181. data/lib/rubocop/cop/style/redundant_condition.rb +30 -13
  182. data/lib/rubocop/cop/style/redundant_double_splat_hash_braces.rb +6 -0
  183. data/lib/rubocop/cop/style/redundant_exception.rb +15 -3
  184. data/lib/rubocop/cop/style/redundant_fetch_block.rb +9 -1
  185. data/lib/rubocop/cop/style/redundant_freeze.rb +10 -1
  186. data/lib/rubocop/cop/style/redundant_parentheses.rb +12 -1
  187. data/lib/rubocop/cop/style/redundant_regexp_argument.rb +7 -0
  188. data/lib/rubocop/cop/style/redundant_regexp_constructor.rb +20 -0
  189. data/lib/rubocop/cop/style/redundant_regexp_escape.rb +4 -0
  190. data/lib/rubocop/cop/style/regexp_literal.rb +75 -26
  191. data/lib/rubocop/cop/style/require_order.rb +9 -0
  192. data/lib/rubocop/cop/style/rescue_modifier.rb +5 -9
  193. data/lib/rubocop/cop/style/return_nil.rb +17 -2
  194. data/lib/rubocop/cop/style/return_nil_in_predicate_method_definition.rb +11 -1
  195. data/lib/rubocop/cop/style/sample.rb +9 -8
  196. data/lib/rubocop/cop/style/self_assignment.rb +12 -15
  197. data/lib/rubocop/cop/style/semicolon.rb +16 -5
  198. data/lib/rubocop/cop/style/single_line_methods.rb +16 -4
  199. data/lib/rubocop/cop/style/stabby_lambda_parentheses.rb +29 -5
  200. data/lib/rubocop/cop/style/string_concatenation.rb +9 -3
  201. data/lib/rubocop/cop/style/super_arguments.rb +3 -0
  202. data/lib/rubocop/cop/style/symbol_proc.rb +23 -5
  203. data/lib/rubocop/cop/style/ternary_parentheses.rb +23 -1
  204. data/lib/rubocop/cop/style/trailing_underscore_variable.rb +23 -19
  205. data/lib/rubocop/cop/style/while_until_modifier.rb +1 -1
  206. data/lib/rubocop/cop/style/yoda_condition.rb +24 -9
  207. data/lib/rubocop/cop/style/yoda_expression.rb +10 -1
  208. data/lib/rubocop/cop/team.rb +10 -5
  209. data/lib/rubocop/cop/variable_force/assignment.rb +1 -1
  210. data/lib/rubocop/cop/variable_force/variable.rb +14 -1
  211. data/lib/rubocop/formatter/base_formatter.rb +22 -0
  212. data/lib/rubocop/formatter/formatter_set.rb +4 -0
  213. data/lib/rubocop/formatter/json_formatter.rb +71 -1
  214. data/lib/rubocop/formatter/offense_count_formatter.rb +3 -1
  215. data/lib/rubocop/formatter/pacman_formatter.rb +3 -1
  216. data/lib/rubocop/formatter/sarif_formatter.rb +12 -7
  217. data/lib/rubocop/formatter/worst_offenders_formatter.rb +3 -1
  218. data/lib/rubocop/lsp/runtime.rb +47 -11
  219. data/lib/rubocop/lsp/stdin_runner.rb +11 -5
  220. data/lib/rubocop/lsp.rb +5 -2
  221. data/lib/rubocop/mcp/autocorrection_request.rb +52 -0
  222. data/lib/rubocop/mcp/explain_tool.rb +68 -0
  223. data/lib/rubocop/mcp/scope.rb +65 -0
  224. data/lib/rubocop/mcp/server.rb +184 -76
  225. data/lib/rubocop/mcp/source_files.rb +35 -0
  226. data/lib/rubocop/offense_limit.rb +30 -0
  227. data/lib/rubocop/options.rb +45 -10
  228. data/lib/rubocop/plugin/configuration_integrator.rb +12 -5
  229. data/lib/rubocop/remote_config.rb +4 -1
  230. data/lib/rubocop/result_cache.rb +20 -13
  231. data/lib/rubocop/runner.rb +109 -41
  232. data/lib/rubocop/server/cache.rb +2 -2
  233. data/lib/rubocop/util.rb +69 -0
  234. data/lib/rubocop/version.rb +1 -1
  235. data/lib/rubocop.rb +5 -0
  236. metadata +16 -5
@@ -50,10 +50,17 @@ module RuboCop
50
50
  unless when_node.conditions.first.first_line == when_node.conditions.last.last_line
51
51
  return true
52
52
  end
53
+ return true if ends_with_endless_range?(when_node.conditions.last)
53
54
  return false unless when_node.body
54
55
 
55
56
  same_line?(when_node, when_node.body)
56
57
  end
58
+
59
+ def ends_with_endless_range?(node)
60
+ node.each_node(:range).any? do |range|
61
+ range.end.nil? && range.source_range.end_pos == node.source_range.end_pos
62
+ end
63
+ end
57
64
  end
58
65
  end
59
66
  end
@@ -146,6 +146,13 @@ module RuboCop
146
146
 
147
147
  MSG = 'Freeze mutable objects assigned to constants.'
148
148
 
149
+ # With `Recursive: true`, `.freeze` is appended to a nested literal. When the literal
150
+ # is the last element and spans multiple lines, the trailing comma lands between the literal
151
+ # and the `.freeze` appended in the same pass.
152
+ def self.autocorrect_incompatible_with
153
+ [Style::TrailingCommaInArrayLiteral, Style::TrailingCommaInHashLiteral]
154
+ end
155
+
149
156
  def on_casgn(node)
150
157
  if node.expression.nil? # This is only the case for `CONST += ...` or similar
151
158
  parent = node.parent
@@ -188,10 +195,12 @@ module RuboCop
188
195
  end
189
196
 
190
197
  def strict_check(value)
198
+ return if value.assignment?
191
199
  return if immutable_literal?(value)
192
200
  return if operation_produces_immutable_object?(value)
193
201
  return if frozen_string_literal?(value)
194
202
  return if shareable_constant_value?(value)
203
+ return if parenthesized_immutable_value?(value)
195
204
 
196
205
  true
197
206
  end
@@ -293,12 +302,30 @@ module RuboCop
293
302
  recent_shareable_value? node
294
303
  end
295
304
 
305
+ def parenthesized_immutable_value?(value)
306
+ return false if !value.begin_type? || !value.children.one?
307
+
308
+ inner = value.children.first
309
+
310
+ !inner.assignment? && !strict_check(inner)
311
+ end
312
+
296
313
  def frozen_regexp_or_range_literals?(node)
297
314
  target_ruby_version >= 3.0 && node.type?(:regexp, :range)
298
315
  end
299
316
 
300
317
  def requires_parentheses?(node)
301
- node.range_type? || (node.send_type? && node.loc.dot.nil?)
318
+ if node.range_type? || node.operator_keyword? || unparenthesized_method_call?(node)
319
+ return true
320
+ end
321
+
322
+ node.if_type? && node.ternary?
323
+ end
324
+
325
+ def unparenthesized_method_call?(node)
326
+ return false unless node.call_type?
327
+
328
+ node.loc.dot.nil? || (node.arguments.any? && !node.parenthesized?)
302
329
  end
303
330
 
304
331
  def correct_splat_expansion(corrector, expr, splat_value)
@@ -118,7 +118,8 @@ module RuboCop
118
118
  if node.ternary?
119
119
  node.if_branch
120
120
  else
121
- range_between(node.condition.source_range.end_pos, node.loc.else.begin_pos)
121
+ if_start = node.loc.begin || node.condition.source_range
122
+ range_between(if_start.end_pos, node.loc.else.begin_pos)
122
123
  end
123
124
  end
124
125
 
@@ -11,6 +11,13 @@ module RuboCop
11
11
  # and their combinations, allowing safe replacement when the receiver matches.
12
12
  # It works with variables, instance variables, class variables, and constants.
13
13
  #
14
+ # @safety
15
+ # This cop is unsafe because the receiver may not be an Array. For a Hash,
16
+ # `hash[hash.size - 1]` looks up the key `hash.size - 1`, which `hash[-1]`
17
+ # does not. Even for an Array, `arr[arr.length - n]` wraps around to an
18
+ # element from the end when `n` exceeds the length, while `arr[-n]`
19
+ # returns `nil`.
20
+ #
14
21
  # @example
15
22
  # # bad
16
23
  # arr[arr.count - 2]
@@ -188,8 +195,9 @@ module RuboCop
188
195
  return false
189
196
  end
190
197
  return true if length_receiver.source == array_receiver.source
198
+ return false unless (base_receiver = extract_base_receiver(array_receiver))
191
199
 
192
- !extract_base_receiver(array_receiver).nil?
200
+ base_receiver.source == (extract_base_receiver(length_receiver) || length_receiver).source
193
201
  end
194
202
 
195
203
  def receivers_match_strict?(length_receiver, array_receiver)
@@ -19,6 +19,19 @@ module RuboCop
19
19
 
20
20
  MSG = 'Avoid using nested modifiers.'
21
21
 
22
+ # Operands that would otherwise bind differently next to `&&`/`||`, or
23
+ # not parse at all.
24
+ # @!method low_precedence?(node)
25
+ def_node_matcher :low_precedence?, <<~PATTERN
26
+ {
27
+ [{and or} semantic_operator?]
28
+ [send prefix_not?]
29
+ [if ternary?]
30
+ assignment?
31
+ [{csend yield super} arguments? !parenthesized? !operator_method?]
32
+ }
33
+ PATTERN
34
+
22
35
  def on_while(node)
23
36
  check(node)
24
37
  end
@@ -63,8 +76,10 @@ module RuboCop
63
76
  end
64
77
 
65
78
  def left_hand_operand(node, operator)
66
- expr = node.condition.source
67
- expr = "(#{expr})" if node.condition.or_type? && operator == '&&'
79
+ expr = operand_source(node.condition)
80
+ if (node.condition.or_type? && operator == '&&') || low_precedence?(node.condition)
81
+ expr = "(#{expr})"
82
+ end
68
83
  expr
69
84
  end
70
85
 
@@ -72,17 +87,20 @@ module RuboCop
72
87
  condition = node.condition
73
88
  negated = left_hand_keyword != node.keyword
74
89
 
75
- expr = if condition.send_type? && !condition.arguments.empty? &&
76
- !condition.operator_method?
77
- add_parentheses_to_method_arguments(condition)
78
- else
79
- condition.source
80
- end
90
+ expr = operand_source(condition)
81
91
  expr = "(#{expr})" if requires_parens?(condition, negated)
82
92
  expr = "!#{expr}" if negated
83
93
  expr
84
94
  end
85
95
 
96
+ def operand_source(condition)
97
+ if condition.send_type? && !condition.arguments.empty? && !condition.operator_method?
98
+ add_parentheses_to_method_arguments(condition)
99
+ else
100
+ condition.source
101
+ end
102
+ end
103
+
86
104
  def add_parentheses_to_method_arguments(send_node)
87
105
  expr = +''
88
106
  expr << "#{send_node.receiver.source}." if send_node.receiver
@@ -97,6 +115,7 @@ module RuboCop
97
115
  # whole expression (e.g. `!(a && b)`, not `!a && b`).
98
116
  (negated && node.operator_keyword?) ||
99
117
  node.or_type? ||
118
+ low_precedence?(node) ||
100
119
  !(RuboCop::AST::Node::COMPARISON_OPERATORS & node.children).empty?
101
120
  end
102
121
  end
@@ -26,7 +26,7 @@ module RuboCop
26
26
 
27
27
  node.each_descendant(:if).select(&:ternary?).each do |nested_ternary|
28
28
  add_offense(nested_ternary) do |corrector|
29
- next if part_of_ignored_node?(node)
29
+ next if part_of_ignored_node?(node) || contains_heredoc?(node)
30
30
 
31
31
  autocorrect(corrector, node)
32
32
  ignore_node(node)
@@ -36,21 +36,39 @@ module RuboCop
36
36
 
37
37
  private
38
38
 
39
+ def contains_heredoc?(node)
40
+ node.each_descendant(:any_str).any?(&:heredoc?)
41
+ end
42
+
39
43
  def autocorrect(corrector, if_node)
40
44
  replace_loc_and_whitespace(corrector, if_node.loc.question, "\n")
41
45
  replace_loc_and_whitespace(corrector, if_node.loc.colon, "\nelse\n")
42
- corrector.replace(if_node.if_branch, remove_parentheses(if_node.if_branch.source))
43
- corrector.wrap(if_node, 'if ', "\nend")
44
- end
46
+ remove_parentheses(corrector, if_node.if_branch)
45
47
 
46
- def remove_parentheses(source)
47
- if source.start_with?('(') && source.end_with?(')')
48
- source.delete_prefix('(').delete_suffix(')')
48
+ if modifier_position?(if_node)
49
+ corrector.wrap(if_node, '(if ', "\nend)")
49
50
  else
50
- source
51
+ corrector.wrap(if_node, 'if ', "\nend")
51
52
  end
52
53
  end
53
54
 
55
+ def remove_parentheses(corrector, node)
56
+ return unless node.begin_type? && node.parenthesized_call?
57
+
58
+ corrector.remove(node.loc.begin)
59
+ corrector.remove(node.loc.end)
60
+ end
61
+
62
+ # An `if` right after `return`, as the operand of `not` or `defined?`, or as
63
+ # the first argument of a call without parentheses would be parsed as a modifier.
64
+ def modifier_position?(node)
65
+ return false unless (parent = node.parent)
66
+ return true if parent.type?(:return, :break, :next)
67
+ return false if !parent.type?(:call, :super, :yield, :defined?) || parent.parenthesized?
68
+
69
+ parent.prefix_not? || parent.first_argument.equal?(node)
70
+ end
71
+
54
72
  def replace_loc_and_whitespace(corrector, range, replacement)
55
73
  corrector.replace(
56
74
  range_with_surrounding_space(range: range, whitespace: true),
@@ -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?
@@ -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)
@@ -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)
@@ -222,7 +229,17 @@ module RuboCop
222
229
  end
223
230
 
224
231
  def valid_begin_assignment?(node)
225
- node.parent&.assignment? && !node.children.one?
232
+ node.parent&.assignment? && (!node.children.one? || too_long_when_joined?(node))
233
+ end
234
+
235
+ def too_long_when_joined?(node)
236
+ statement = node.children.first
237
+ max = config.for_enabled_cop('Layout/LineLength')['Max']
238
+ return false unless max && statement.single_line?
239
+
240
+ length = statement.source.length
241
+ length += 2 if low_precedence_statement?(statement)
242
+ node.loc.column + length > max
226
243
  end
227
244
 
228
245
  def inspect_branches(node)