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
@@ -21,6 +21,10 @@ module RuboCop
21
21
  MSG_NEWLINE = 'Do not use `%<keyword>s %<expr>s;` - use a newline instead.'
22
22
  MSG_TERNARY = 'Do not use `%<keyword>s %<expr>s;` - use a ternary operator instead.'
23
23
 
24
+ LOWER_PRECEDENCE_THAN_TERNARY_TYPES = %i[
25
+ and or if rescue any_match_pattern while until while_post until_post
26
+ ].freeze
27
+
24
28
  def on_normal_if_unless(node)
25
29
  return if node.parent&.if_type?
26
30
  return if part_of_ignored_node?(node)
@@ -77,12 +81,14 @@ module RuboCop
77
81
  def replacement(node)
78
82
  return correct_elsif(node) if node.else_branch&.if_type?
79
83
 
80
- then_code = node.if_branch ? build_expression(node.if_branch) : 'nil'
81
- else_code = node.else_branch ? build_expression(node.else_branch) : 'nil'
84
+ then_code = node.if_branch ? ternary_operand(node.if_branch) : 'nil'
85
+ else_code = node.else_branch ? ternary_operand(node.else_branch) : 'nil'
82
86
 
83
87
  then_code, else_code = else_code, then_code if node.unless?
84
88
 
85
- "#{ternary_condition(node)} ? #{then_code} : #{else_code}"
89
+ ternary = "#{ternary_condition(node)} ? #{then_code} : #{else_code}"
90
+
91
+ operand_of_parent?(node) ? "(#{ternary})" : ternary
86
92
  end
87
93
 
88
94
  def correct_elsif(node)
@@ -108,7 +114,34 @@ module RuboCop
108
114
  # `(a = b) ? c : d`), changing what gets assigned.
109
115
  def ternary_condition(node)
110
116
  condition = node.condition
111
- condition.assignment? ? "(#{condition.source})" : condition.source
117
+ condition.assignment? ? "(#{condition.source})" : ternary_operand(condition)
118
+ end
119
+
120
+ def ternary_operand(expr)
121
+ lower_precedence_than_ternary?(expr) ? "(#{expr.source})" : build_expression(expr)
122
+ end
123
+
124
+ # Expressions that bind looser than `?:` (or cannot appear unparenthesized
125
+ # inside it) would otherwise absorb or break the ternary.
126
+ def lower_precedence_than_ternary?(node)
127
+ return true if node.type?(*LOWER_PRECEDENCE_THAN_TERNARY_TYPES)
128
+ return true if node.send_type? && (node.prefix_not? || prefix_bang_on_command?(node))
129
+
130
+ node.type?(:yield, :super, :defined?) && node.arguments.any? && !node.parenthesized?
131
+ end
132
+
133
+ def operand_of_parent?(node)
134
+ return false unless (parent = node.parent)
135
+ return true if parent.operator_keyword?
136
+
137
+ parent.call_type? && call_operand?(parent, node)
138
+ end
139
+
140
+ def call_operand?(call, node)
141
+ return false if call.prefix_not?
142
+ return true if call.receiver.equal?(node)
143
+
144
+ call.operator_method? && !call.method?(:[]) && !call.method?(:[]=)
112
145
  end
113
146
 
114
147
  def build_else_branch(second_condition)
@@ -131,6 +164,16 @@ module RuboCop
131
164
  result
132
165
  end
133
166
 
167
+ # `!foo a` leaves the arguments of `foo` unparenthesized at the end of the operand,
168
+ # where they would absorb the rest of the ternary.
169
+ def prefix_bang_on_command?(node)
170
+ return false unless node.prefix_bang?
171
+
172
+ receiver = node.receiver
173
+
174
+ require_argument_parentheses?(receiver) || lower_precedence_than_ternary?(receiver)
175
+ end
176
+
134
177
  def require_argument_parentheses?(node)
135
178
  return false if !node.call_type? || node.arithmetic_operation?
136
179
 
@@ -114,9 +114,31 @@ module RuboCop
114
114
  method_call, _lhs, method, _rhs = inverse_candidate?(node)
115
115
  return unless method_call && method
116
116
 
117
- corrector.remove(not_to_receiver(node, method_call))
117
+ remove_negation(corrector, node, method, method_call)
118
118
  corrector.replace(method_call.loc.selector, inverse_methods[method].to_s)
119
- remove_end_parenthesis(corrector, node, method, method_call)
119
+ end
120
+
121
+ def remove_negation(corrector, node, method, method_call)
122
+ if !keep_parentheses?(node, method_call)
123
+ corrector.remove(not_to_receiver(node, method_call))
124
+ remove_end_parenthesis(corrector, node, method, method_call)
125
+ elsif node.parenthesized?
126
+ corrector.remove(node.loc.selector)
127
+ else
128
+ corrector.remove(node.loc.selector.join(node.receiver.source_range.begin))
129
+ end
130
+ end
131
+
132
+ def keep_parentheses?(node, method_call)
133
+ return false unless node.receiver.begin_type? || node.parenthesized?
134
+ return false unless method_call.operator_method?
135
+ return false unless (parent = node.parent)&.call_type?
136
+
137
+ binary_operator?(parent) || parent.receiver.equal?(node)
138
+ end
139
+
140
+ def binary_operator?(node)
141
+ node.operator_method? && !node.loc.dot && !node.method?(:[]) && !node.method?(:[]=)
120
142
  end
121
143
 
122
144
  def correct_inverse_block(corrector, node)
@@ -61,6 +61,12 @@ module RuboCop
61
61
 
62
62
  minimum_target_ruby_version 3.4
63
63
 
64
+ # `Style/StabbyLambdaParentheses` wraps the parameter this cop removes in the same pass,
65
+ # which would leave an empty parameter list that forbids `it`.
66
+ def self.autocorrect_incompatible_with
67
+ [Style::Lambda, Style::StabbyLambdaParentheses]
68
+ end
69
+
64
70
  def on_block(node)
65
71
  return unless style == :always
66
72
  return unless node.arguments.one?
@@ -68,9 +74,10 @@ module RuboCop
68
74
  # `restarg`, `kwrestarg`, `blockarg` nodes can return early.
69
75
  return unless node.first_argument.arg_type?
70
76
 
71
- variables = find_block_variables(node, node.first_argument.source)
77
+ block_argument_name = node.first_argument.source
78
+ return if used_within_inner_block?(node.body, block_argument_name)
72
79
 
73
- variables.each do |variable|
80
+ find_block_variables(node, block_argument_name).each do |variable|
74
81
  add_offense(variable, message: MSG_USE_IT_PARAMETER) do |corrector|
75
82
  corrector.remove(node.arguments)
76
83
  corrector.replace(variable, 'it')
@@ -111,7 +118,29 @@ module RuboCop
111
118
  def find_block_variables(node, block_argument_name)
112
119
  return [] unless node.body
113
120
 
114
- node.body.each_descendant(:lvar).select do |descendant|
121
+ ranges = inner_block_ranges(node.body)
122
+
123
+ block_variables(node.body, block_argument_name).reject do |variable|
124
+ ranges.any? { |range| range.contains?(variable.source_range) }
125
+ end
126
+ end
127
+
128
+ def inner_block_ranges(body)
129
+ body.each_node(:any_block).map { |block| block.loc.begin.join(block.loc.end) }
130
+ end
131
+
132
+ def used_within_inner_block?(body, block_argument_name)
133
+ return false unless body
134
+
135
+ ranges = inner_block_ranges(body)
136
+
137
+ block_variables(body, block_argument_name).any? do |variable|
138
+ ranges.any? { |range| range.contains?(variable.source_range) }
139
+ end
140
+ end
141
+
142
+ def block_variables(body, block_argument_name)
143
+ body.each_descendant(:lvar).select do |descendant|
115
144
  descendant.source == block_argument_name
116
145
  end
117
146
  end
@@ -75,7 +75,9 @@ module RuboCop
75
75
 
76
76
  def remove_kwargs(kwarg_nodes, corrector)
77
77
  kwarg_nodes.each do |kwarg|
78
- with_space = range_with_surrounding_space(kwarg.source_range)
78
+ range = range_with_surrounding_space(kwarg.source_range, side: :left, whitespace: true)
79
+ with_space = range_with_surrounding_space(range, side: :right)
80
+
79
81
  corrector.remove(range_with_surrounding_comma(with_space, :left))
80
82
  end
81
83
  end
@@ -48,6 +48,8 @@ module RuboCop
48
48
  # end
49
49
  class Lambda < Base
50
50
  include ConfigurableEnforcedStyle
51
+ include PatternMatchingHelp
52
+ include RescueNode
51
53
  extend AutoCorrector
52
54
 
53
55
  LITERAL_MESSAGE = 'Use the `-> { ... }` lambda literal syntax for %<modifier>s lambdas.'
@@ -71,6 +73,7 @@ module RuboCop
71
73
  selector = node.send_node.source
72
74
 
73
75
  return unless offending_selector?(node, selector)
76
+ return if uncorrectable_lambda_literal?(node)
74
77
 
75
78
  add_offense(node.send_node, message: message(node, selector)) do |corrector|
76
79
  if node.send_node.lambda_literal?
@@ -91,6 +94,21 @@ module RuboCop
91
94
  selector == OFFENDING_SELECTORS[:style][style][lines]
92
95
  end
93
96
 
97
+ def uncorrectable_lambda_literal?(node)
98
+ return false unless node.send_node.lambda_literal?
99
+ return true if in_pattern?(node)
100
+ return false unless rescue_or_ensure_clause?(node.body)
101
+
102
+ LambdaLiteralToMethodCorrector.new(node).replace_delimiters?
103
+ end
104
+
105
+ def rescue_or_ensure_clause?(body)
106
+ return false unless body
107
+ return true if body.ensure_type?
108
+
109
+ body.rescue_type? && !rescue_modifier?(body.resbody_branches.first)
110
+ end
111
+
94
112
  def message(node, selector)
95
113
  message = selector == '->' ? METHOD_MESSAGE : LITERAL_MESSAGE
96
114
 
@@ -28,8 +28,9 @@ module RuboCop
28
28
  def on_send(node)
29
29
  return unless node.receiver
30
30
  # Rewriting the call rebuilds it as a single expression, which would drop
31
- # any comments inside the argument list, so leave it to be fixed manually.
32
- return if comments_in_node?(node)
31
+ # any comments or heredoc bodies inside the argument list, so leave it to be
32
+ # fixed manually.
33
+ return if comments_in_node?(node) || heredoc_argument?(node)
33
34
 
34
35
  if offense?(node)
35
36
  prefer = prefer(node)
@@ -59,6 +60,10 @@ module RuboCop
59
60
  end
60
61
  end
61
62
 
63
+ def heredoc_argument?(node)
64
+ node.arguments.any? { |argument| argument.each_node(:any_str).any?(&:heredoc?) }
65
+ end
66
+
62
67
  def offense?(node)
63
68
  (explicit_style? && node.implicit_call?) || (implicit_style? && !node.implicit_call?)
64
69
  end
@@ -112,8 +112,7 @@ module RuboCop
112
112
 
113
113
  def on_block(node)
114
114
  return unless each_block_with_push?(node)
115
-
116
- dest_var = find_dest_var(node)
115
+ return unless (dest_var = find_dest_var(node))
117
116
 
118
117
  if offending_empty_array_tap?(node, dest_var)
119
118
  asgn = dest_var.declaration_node
@@ -40,6 +40,8 @@ module RuboCop
40
40
  return if legitimate_call_with_parentheses?(node)
41
41
  return if allowed_camel_case_method_call?(node)
42
42
  return if allowed_string_interpolation_method_call?(node)
43
+ return if code_after_closing_parenthesis_on_its_own_line?(node)
44
+ return if comment_before_first_argument?(node)
43
45
 
44
46
  (@pending_omit_offenses ||= []) << node
45
47
  end
@@ -104,11 +106,30 @@ module RuboCop
104
106
  inside_string_interpolation?(node)
105
107
  end
106
108
 
109
+ def code_after_closing_parenthesis_on_its_own_line?(node)
110
+ closing = node.loc.end
111
+ return false if node.arguments.none? || node.last_argument.last_line == closing.line
112
+
113
+ rest = closing.source_line[(closing.column + 1)..].strip
114
+
115
+ !rest.empty? && !rest.start_with?('#')
116
+ end
117
+
118
+ # Without the parentheses, a comment before the first argument would end the call there,
119
+ # and a line continuation can't carry the call past a comment.
120
+ def comment_before_first_argument?(node)
121
+ return false unless (first_argument = node.first_argument)
122
+
123
+ lines = node.loc.begin.line...first_argument.first_line
124
+ processed_source.each_comment_in_lines(lines).any?
125
+ end
126
+
107
127
  def parentheses_at_the_end_of_multiline_call?(node)
108
- node.multiline? &&
109
- node.loc.begin.source_line
110
- .gsub(TRAILING_WHITESPACE_REGEX, '')
111
- .end_with?('(')
128
+ return false unless node.multiline?
129
+
130
+ opening = node.loc.begin
131
+
132
+ opening.source_line[(opening.column + 1)..].gsub(TRAILING_WHITESPACE_REGEX, '').empty?
112
133
  end
113
134
 
114
135
  def legitimate_call_with_parentheses?(node) # rubocop:disable Metrics/PerceivedComplexity
@@ -137,9 +158,13 @@ module RuboCop
137
158
  parent = node.parent&.any_block_type? ? node.parent.parent : node.parent
138
159
  return false unless parent
139
160
 
140
- logical_operator?(parent) ||
141
- (parent.send_type? &&
142
- parent.arguments.any? { |argument| logical_operator?(argument) })
161
+ if logical_operator?(parent) || command_assignment_before_keyword_operator?(parent)
162
+ return true
163
+ end
164
+
165
+ parent.send_type? && parent.arguments.any? do |argument|
166
+ logical_operator?(argument)
167
+ end
143
168
  end
144
169
 
145
170
  def call_in_optional_arguments?(node)
@@ -229,6 +254,15 @@ module RuboCop
229
254
  node.operator_keyword? && node.logical_operator?
230
255
  end
231
256
 
257
+ def command_assignment_before_keyword_operator?(node)
258
+ return false unless node.assignment?
259
+
260
+ node = node.parent while node.parent&.assignment?
261
+ return false unless (operator = node.parent)
262
+
263
+ operator.operator_keyword? && operator.semantic_operator?
264
+ end
265
+
232
266
  def hash_literal?(node)
233
267
  node.hash_type? && node.braces?
234
268
  end
@@ -262,10 +296,11 @@ module RuboCop
262
296
  end
263
297
 
264
298
  def forwards_anonymous_rest_arguments?(node)
265
- return false unless (last_argument = node.last_argument)
266
- return true if last_argument.forwarded_restarg_type?
299
+ node.arguments.any? do |argument|
300
+ next true if argument.forwarded_restarg_type?
267
301
 
268
- last_argument.hash_type? && last_argument.children.any?(&:forwarded_kwrestarg_type?)
302
+ argument.hash_type? && argument.children.any?(&:forwarded_kwrestarg_type?)
303
+ end
269
304
  end
270
305
  end
271
306
  end
@@ -242,7 +242,13 @@ module RuboCop
242
242
  extend AutoCorrector
243
243
 
244
244
  def self.autocorrect_incompatible_with
245
- [Style::NestedParenthesizedCalls, Style::RescueModifier, Style::TrailingCommaInArguments]
245
+ [
246
+ Style::NestedParenthesizedCalls,
247
+ Style::RedundantParentheses,
248
+ Style::RescueModifier,
249
+ Style::TernaryParentheses,
250
+ Style::TrailingCommaInArguments
251
+ ]
246
252
  end
247
253
 
248
254
  def on_send(node)
@@ -269,7 +275,24 @@ module RuboCop
269
275
  return false unless node.arguments.one?
270
276
 
271
277
  first_node = node.first_argument
272
- first_node.begin_type? && first_node.parenthesized_call?
278
+ return false unless first_node.begin_type? && first_node.parenthesized_call?
279
+
280
+ first_node.children.one? && mergeable_parentheses?(first_node.children.first)
281
+ end
282
+
283
+ def mergeable_parentheses?(node)
284
+ case node.type
285
+ when :and, :or
286
+ !node.semantic_operator?
287
+ when :if, :while, :until
288
+ !node.modifier_form?
289
+ when :send
290
+ !node.keyword?
291
+ when :rescue, :masgn, :match_pattern, :match_pattern_p
292
+ false
293
+ else
294
+ true
295
+ end
273
296
  end
274
297
  end
275
298
  end
@@ -29,10 +29,16 @@ module RuboCop
29
29
  class MethodCallWithoutArgsParentheses < Base
30
30
  include AllowedMethods
31
31
  include AllowedPattern
32
+ include ReparsedEquivalence
32
33
  extend AutoCorrector
33
34
 
34
35
  MSG = 'Do not use parentheses for method calls with no arguments.'
35
36
 
37
+ def on_new_investigation
38
+ @pending_ranges = []
39
+ super
40
+ end
41
+
36
42
  # rubocop:disable-next Metrics/CyclomaticComplexity
37
43
  def on_send(node)
38
44
  return unless !node.arguments? && node.parenthesized?
@@ -46,15 +52,32 @@ module RuboCop
46
52
  end
47
53
  alias on_csend on_send
48
54
 
55
+ # The parentheses are significant when removing them changes how the
56
+ # code parses, e.g. when a local variable with the same name is in
57
+ # scope (`foo = 1; foo()`) or an operator follows (`foo() -1`). The
58
+ # candidates are verified together, since outside a method definition
59
+ # each one would otherwise reparse the whole file.
60
+ def on_investigation_end
61
+ verified_by_reparse(@pending_ranges).each do |range|
62
+ add_offense(range) do |corrector|
63
+ apply_reparse_correction(corrector, range)
64
+ end
65
+ end
66
+
67
+ super
68
+ end
69
+
49
70
  private
50
71
 
51
72
  def register_offense(node)
52
73
  range = offense_range(node)
53
74
  return if processed_source.contains_comment?(range)
54
75
 
55
- add_offense(range) do |corrector|
56
- corrector.remove(range)
57
- end
76
+ @pending_ranges << range
77
+ end
78
+
79
+ def apply_reparse_correction(corrector, range)
80
+ corrector.remove(range)
58
81
  end
59
82
 
60
83
  def ineligible_node?(node)
@@ -127,7 +127,19 @@ module RuboCop
127
127
 
128
128
  def correct_arguments(arg_node, corrector)
129
129
  corrector.replace(arg_node.loc.begin, ' ')
130
- corrector.remove(arg_node.loc.end)
130
+
131
+ if code_after_arguments?(arg_node)
132
+ corrector.replace(arg_node.loc.end, ';')
133
+ else
134
+ corrector.remove(arg_node.loc.end)
135
+ end
136
+ end
137
+
138
+ def code_after_arguments?(arg_node)
139
+ closing = arg_node.loc.end
140
+ rest = closing.source_line[(closing.column + 1)..].strip
141
+
142
+ !rest.empty? && !rest.start_with?(';', '#')
131
143
  end
132
144
 
133
145
  def forced_parentheses?(node)
@@ -137,8 +149,11 @@ module RuboCop
137
149
  # 3. Argument lists containing an anonymous rest arguments forwarding (`*`)
138
150
  # 4. Argument lists containing an anonymous keyword rest arguments forwarding (`**`)
139
151
  # 5. Argument lists containing an anonymous block forwarding (`&`)
140
- # Removing the parens would be a syntax error here.
141
- node.endless? || anonymous_arguments?(node)
152
+ # 6. Argument lists that begin on a line below the method name
153
+ # 7. Argument lists that begin with a destructuring parameter (`(a, b)`)
154
+ # Removing the parens would be a syntax error, or a change of the parameters, here.
155
+ node.endless? || anonymous_arguments?(node) || arguments_on_own_line?(node) ||
156
+ destructuring_first_argument?(node)
142
157
  end
143
158
 
144
159
  def require_parentheses?(args)
@@ -176,6 +191,17 @@ module RuboCop
176
191
 
177
192
  last_argument.blockarg_type? && last_argument.name.nil?
178
193
  end
194
+
195
+ def arguments_on_own_line?(node)
196
+ return false unless (first_argument = node.first_argument)
197
+ return false unless parentheses?(node.arguments)
198
+
199
+ node.arguments.loc.begin.line != first_argument.first_line
200
+ end
201
+
202
+ def destructuring_first_argument?(node)
203
+ node.first_argument&.mlhs_type?
204
+ end
179
205
  end
180
206
  end
181
207
  end
@@ -125,13 +125,19 @@ module RuboCop
125
125
  private
126
126
 
127
127
  def check(node)
128
- return if node.else?
128
+ return if node.else? || guard_clause_cop_conflict?(node)
129
129
 
130
130
  add_offense(node, message: format(message_template, type: node.type)) do |corrector|
131
131
  autocorrect(corrector, node)
132
132
  end
133
133
  end
134
134
 
135
+ def guard_clause_cop_conflict?(node)
136
+ return false unless node.if_type? && @config.cop_enabled?('Style/GuardClause')
137
+
138
+ [node.if_branch, node.else_branch].compact.any?(&:guard_clause?)
139
+ end
140
+
135
141
  def message_template
136
142
  case empty_else_style
137
143
  when :empty
@@ -17,6 +17,7 @@ module RuboCop
17
17
  class MultilineIfModifier < Base
18
18
  include StatementModifier
19
19
  include Alignment
20
+ include RangeHelp
20
21
  extend AutoCorrector
21
22
 
22
23
  MSG = 'Favor a normal %<keyword>s-statement over a modifier ' \
@@ -27,31 +28,70 @@ module RuboCop
27
28
  return unless node.modifier_form? && node.body.multiline?
28
29
 
29
30
  add_offense(node, message: format(MSG, keyword: node.keyword)) do |corrector|
30
- corrector.replace(node, to_normal_if(node))
31
+ autocorrect(corrector, node)
31
32
  end
32
33
  ignore_node(node)
33
34
  end
34
35
 
35
36
  private
36
37
 
37
- def to_normal_if(node)
38
+ def autocorrect(corrector, node)
39
+ condition_heredoc, body_heredoc = [node.condition, node.body].map do |part|
40
+ next unless (range = heredoc_after(part, node))
41
+
42
+ corrector.remove(range)
43
+ range.source.chomp
44
+ end
45
+ corrector.replace(node, to_normal_if(node, condition_heredoc, body_heredoc))
46
+ end
47
+
48
+ # Heredocs opened on the modifier line have their bodies after it, so
49
+ # they are moved along with the part of the `if` they belong to.
50
+ def heredoc_after(part, node)
51
+ ranges = part.each_node(:any_str).select(&:heredoc?).map do |heredoc|
52
+ heredoc.loc.heredoc_body.join(heredoc.loc.heredoc_end)
53
+ end
54
+ ranges.select! { |range| range.line > node.last_line }
55
+ return if ranges.empty?
56
+
57
+ range_by_whole_lines(ranges.reduce(:join), include_final_newline: true)
58
+ end
59
+
60
+ def to_normal_if(node, condition_heredoc, body_heredoc)
38
61
  indented_body = indented_body(node.body, node)
39
62
  condition = "#{node.keyword} #{node.condition.source}"
40
63
  indented_end = "#{offset(node)}end"
41
64
 
42
- [condition, indented_body, indented_end].join("\n")
65
+ lines = [condition, condition_heredoc, indented_body, body_heredoc, indented_end]
66
+ lines.compact.join("\n")
43
67
  end
44
68
 
45
69
  def indented_body(body, node)
46
70
  body_source = "#{offset(node)}#{body.source}"
47
- body_source.each_line.map do |line|
48
- if line == "\n"
71
+ verbatim_lines = verbatim_lines_in(body)
72
+ body_source.each_line.with_index(body.first_line).map do |line, line_number|
73
+ if line == "\n" || verbatim_lines.include?(line_number)
49
74
  line
50
75
  else
51
76
  line.sub(/^\s{#{offset(node).length}}/, indentation(node))
52
77
  end
53
78
  end.join
54
79
  end
80
+
81
+ def verbatim_lines_in(body)
82
+ body.each_node(:any_str).filter_map { |str| verbatim_line_range(str) }.flat_map(&:to_a)
83
+ end
84
+
85
+ # The indentation of a line that starts inside a string is part of the
86
+ # string. Only squiggly heredocs strip it from their bodies, and a `<<`
87
+ # terminator has to stay at the start of its line.
88
+ def verbatim_line_range(str)
89
+ if str.heredoc?
90
+ str.loc.heredoc_body.line..str.loc.heredoc_end.line unless str.source.start_with?('<<~')
91
+ elsif str.loc?(:begin) && str.loc?(:end)
92
+ (str.first_line + 1)..str.last_line
93
+ end
94
+ end
55
95
  end
56
96
  end
57
97
  end
@@ -50,10 +50,17 @@ module RuboCop
50
50
  # Requires `then` for write `in` and its body on the same line.
51
51
  def require_then?(in_pattern_node)
52
52
  return true unless in_pattern_node.pattern.single_line?
53
+ return true if ends_with_endless_range?(in_pattern_node.pattern)
53
54
  return false unless in_pattern_node.body
54
55
 
55
56
  same_line?(in_pattern_node, in_pattern_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
@@ -27,6 +27,7 @@ module RuboCop
27
27
  def on_def(node)
28
28
  return unless node.arguments?
29
29
  return if opening_line(node) == closing_line(node)
30
+ return if node.arguments.any?(&:multiline?)
30
31
  return if correction_exceeds_max_line_length?(node)
31
32
  return unless (begin_of_arguments = node.arguments.loc.begin)
32
33
 
@@ -78,11 +79,7 @@ module RuboCop
78
79
  def correction_exceeds_max_line_length?(node)
79
80
  return false unless max_line_length
80
81
 
81
- indentation_width(node) + definition_width(node) > max_line_length
82
- end
83
-
84
- def indentation_width(node)
85
- processed_source.line_indentation(node.source_range.line)
82
+ node.source_range.column + definition_width(node) > max_line_length
86
83
  end
87
84
 
88
85
  def definition_width(node)
@@ -7,7 +7,8 @@ module RuboCop
7
7
  #
8
8
  # NOTE: `return if ... else ... end` is syntax error. If `return` is used before
9
9
  # multiline ternary operator expression, it will be autocorrected to single-line
10
- # ternary operator. The same is true for `break`, `next`, and method call.
10
+ # ternary operator. The same is true for `break`, `next`, `yield`, `super`,
11
+ # `defined?` without parentheses, and method call.
11
12
  #
12
13
  # @example
13
14
  # # bad
@@ -39,7 +40,7 @@ module RuboCop
39
40
 
40
41
  MSG_IF = 'Avoid multi-line ternary operators, use `if` or `unless` instead.'
41
42
  MSG_SINGLE_LINE = 'Avoid multi-line ternary operators, use single-line instead.'
42
- SINGLE_LINE_TYPES = %i[return break next send csend].freeze
43
+ SINGLE_LINE_TYPES = %i[return break next send csend super yield].freeze
43
44
 
44
45
  def on_if(node)
45
46
  return unless offense?(node)
@@ -90,7 +91,10 @@ module RuboCop
90
91
  end
91
92
 
92
93
  def enforce_single_line_ternary_operator?(node)
93
- SINGLE_LINE_TYPES.include?(node.parent&.type) && !use_assignment_method?(node.parent)
94
+ return false unless (parent = node.parent)
95
+ return !parent.parenthesized? if parent.defined_type?
96
+
97
+ SINGLE_LINE_TYPES.include?(parent.type) && !use_assignment_method?(parent)
94
98
  end
95
99
 
96
100
  def use_assignment_method?(node)