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,8 +50,11 @@ module RuboCop
50
50
  (block $_ (args $...) (yield $...))
51
51
  PATTERN
52
52
 
53
+ # `Style/LambdaCall` rewrites the `call` that the block argument is appended to, and
54
+ # `Style/TrailingCommaInArguments` changes the trailing comma where it is appended,
55
+ # in the same pass.
53
56
  def self.autocorrect_incompatible_with
54
- [Lint::UnusedMethodArgument]
57
+ [Lint::UnusedMethodArgument, Style::LambdaCall, Style::TrailingCommaInArguments]
55
58
  end
56
59
 
57
60
  def initialize(config = nil, options = nil)
@@ -70,10 +73,13 @@ module RuboCop
70
73
  # this is not a valid ruby pattern, but can happen in haml or erb,
71
74
  # so this can cause crashes in haml_lint
72
75
  return unless def_node
76
+ return if argument_forwarding?(def_node)
73
77
 
74
78
  block_name = extract_block_name(def_node)
75
79
 
76
80
  add_offense(block_node) do |corrector|
81
+ next unless can_add_block_argument?(def_node, send_node, block_name)
82
+
77
83
  corrector.remove(block_body_range(block_node, send_node))
78
84
 
79
85
  add_block_argument(send_node, corrector, block_name)
@@ -84,6 +90,11 @@ module RuboCop
84
90
 
85
91
  private
86
92
 
93
+ # A block parameter can't be added next to `...`.
94
+ def argument_forwarding?(def_node)
95
+ def_node.arguments.any?(&:forward_arg_type?)
96
+ end
97
+
87
98
  def extract_block_name(def_node)
88
99
  if def_node.block_argument?
89
100
  def_node.last_argument.name
@@ -92,6 +103,27 @@ module RuboCop
92
103
  end
93
104
  end
94
105
 
106
+ def can_add_block_argument?(def_node, send_node, block_name)
107
+ return false if block_name_taken?(def_node, block_name)
108
+
109
+ !send_node.zsuper_type? || zsuper_arguments_forwardable?(def_node)
110
+ end
111
+
112
+ def block_name_taken?(def_node, block_name)
113
+ return false if def_node.block_argument?
114
+
115
+ def_node.each_descendant(:argument, :lvasgn).any? { |node| node.name.to_s == block_name }
116
+ end
117
+
118
+ # A destructuring argument can only be passed on as the original value,
119
+ # and anonymous arguments can only be forwarded since Ruby 3.2.
120
+ def zsuper_arguments_forwardable?(def_node)
121
+ def_node.arguments.none? do |arg|
122
+ arg.mlhs_type? ||
123
+ (arg.type?(:restarg, :kwrestarg) && !arg.name && target_ruby_version < 3.2)
124
+ end
125
+ end
126
+
95
127
  def yielding_arguments?(block_args, yield_args)
96
128
  yield_args = yield_args.dup.fill(
97
129
  nil,
@@ -110,6 +142,8 @@ module RuboCop
110
142
  insert_argument(node, corrector, block_name)
111
143
  elsif empty_arguments?(node)
112
144
  corrector.replace(node.arguments, "(&#{block_name})")
145
+ elsif index_call?(node)
146
+ corrector.replace(node.loc.selector, "[&#{block_name}]")
113
147
  elsif call_like?(node)
114
148
  correct_call_node(node, corrector, block_name)
115
149
  else
@@ -122,6 +156,10 @@ module RuboCop
122
156
  node.arguments.is_a?(RuboCop::AST::Node) && node.arguments.loc.begin
123
157
  end
124
158
 
159
+ def index_call?(node)
160
+ node.send_type? && node.method?(:[]) && !node.loc?(:dot)
161
+ end
162
+
125
163
  def call_like?(node)
126
164
  node.type?(:call, :zsuper, :super)
127
165
  end
@@ -141,19 +179,21 @@ module RuboCop
141
179
  else
142
180
  "&#{block_name}"
143
181
  end
144
- corrector.insert_after(node, "(#{new_arguments})")
145
- return unless node.parenthesized?
146
-
147
- args_begin = Util.args_begin(node)
148
- args_end = Util.args_end(node)
149
- range = range_between(args_begin.begin_pos, args_end.end_pos)
150
- corrector.remove(range)
182
+ if node.parenthesized?
183
+ corrector.replace(node.loc.begin.join(node.loc.end), "(#{new_arguments})")
184
+ else
185
+ corrector.insert_after(node, "(#{new_arguments})")
186
+ end
151
187
  end
152
188
 
153
189
  def build_new_arguments_for_zsuper(node)
154
190
  def_node = node.each_ancestor(:any_def).first
155
- def_node.arguments.map do |arg|
156
- arg.optarg_type? ? arg.node_parts[0] : arg.source
191
+ def_node.arguments.filter_map do |arg|
192
+ case arg.type
193
+ when :arg, :optarg then arg.name.to_s
194
+ when :kwarg, :kwoptarg then "#{arg.name}: #{arg.name}"
195
+ when :restarg, :kwrestarg then arg.source
196
+ end
157
197
  end
158
198
  end
159
199
 
@@ -97,6 +97,7 @@ module RuboCop
97
97
 
98
98
  def on_send(node)
99
99
  return unless offense_condition?(node)
100
+ return if comment_before_to_f?(node)
100
101
 
101
102
  add_offense(node) do |corrector|
102
103
  case style
@@ -141,8 +142,27 @@ module RuboCop
141
142
  end
142
143
 
143
144
  def remove_to_f_method(corrector, send_node)
144
- corrector.remove(send_node.loc.dot)
145
- corrector.remove(send_node.loc.selector)
145
+ corrector.remove(send_node.receiver.source_range.end.join(send_node.loc.selector.end))
146
+ end
147
+
148
+ def comment_before_to_f?(node)
149
+ operands_losing_to_f(node).any? do |operand|
150
+ next false unless to_f_method?(operand)
151
+
152
+ range = operand.receiver.source_range.end.join(operand.loc.selector.begin)
153
+ processed_source.contains_comment?(range)
154
+ end
155
+ end
156
+
157
+ def operands_losing_to_f(node)
158
+ case style
159
+ when :left_coerce, :single_coerce
160
+ [node.first_argument]
161
+ when :right_coerce
162
+ [node.receiver]
163
+ else
164
+ [node.receiver, node.first_argument]
165
+ end
146
166
  end
147
167
 
148
168
  def correct_from_slash_to_fdiv(corrector, receiver, argument)
@@ -52,12 +52,21 @@ module RuboCop
52
52
  EACH_LENGTH = 'each'.length
53
53
  PREFER_EACH = 'Prefer `each` over `for`.'
54
54
  PREFER_FOR = 'Prefer `for` over `each`.'
55
+ IMPLICIT_PARAMETERS = %i[_1 _2 _3 _4 _5 _6 _7 _8 _9 it].to_set.freeze
56
+
57
+ # `Style/RedundantBegin` removes the `begin` of a block body in the same pass,
58
+ # leaving a `rescue` or `ensure` clause directly in the `for` body.
59
+ def self.autocorrect_incompatible_with
60
+ [Style::RedundantBegin]
61
+ end
55
62
 
56
63
  def on_for(node)
57
64
  if style == :each
58
65
  add_offense(node, message: PREFER_EACH) do |corrector|
59
- ForToEachCorrector.new(node).call(corrector)
60
66
  opposite_style_detected
67
+ next unless convertible_to_each?(node)
68
+
69
+ ForToEachCorrector.new(node).call(corrector)
61
70
  end
62
71
  else
63
72
  correct_style_detected
@@ -69,7 +78,7 @@ module RuboCop
69
78
 
70
79
  if style == :for
71
80
  return unless node.receiver
72
- return if rescue_or_ensure_body?(node)
81
+ return if rescue_or_ensure_body?(node) || do_end_block_in_collection?(node)
73
82
 
74
83
  add_offense(node, message: PREFER_FOR) do |corrector|
75
84
  EachToForCorrector.new(node).call(corrector)
@@ -85,6 +94,31 @@ module RuboCop
85
94
 
86
95
  private
87
96
 
97
+ # The loop variable becomes a block parameter, which can only be a local
98
+ # variable, and the body can't refer to an implicit block parameter
99
+ # (`_1` or `it`) once the block has an explicit one.
100
+ def convertible_to_each?(node)
101
+ return false unless node.variable.each_node.all? { |n| n.type?(:lvasgn, :mlhs, :splat) }
102
+ return true unless node.body
103
+
104
+ node.body.each_node(:send, :lvar).none? { |ref| implicit_parameter?(ref, node) }
105
+ end
106
+
107
+ # Inside a numbered or `it` block, `_1` and `it` are local variables
108
+ # rather than method calls.
109
+ def implicit_parameter?(ref, for_node)
110
+ return false unless implicit_parameter_reference?(ref)
111
+
112
+ ref.each_ancestor.take_while { |ancestor| !ancestor.equal?(for_node) }
113
+ .none?(&:any_block_type?)
114
+ end
115
+
116
+ def implicit_parameter_reference?(ref)
117
+ return IMPLICIT_PARAMETERS.include?(ref.name) if ref.lvar_type?
118
+
119
+ IMPLICIT_PARAMETERS.include?(ref.method_name) && !ref.receiver && !ref.arguments?
120
+ end
121
+
88
122
  def suspect_enumerable?(node)
89
123
  node.multiline? && node.method?(:each) && !node.send_node.arguments?
90
124
  end
@@ -92,6 +126,10 @@ module RuboCop
92
126
  def rescue_or_ensure_body?(node)
93
127
  node.body&.type?(:rescue, :ensure)
94
128
  end
129
+
130
+ def do_end_block_in_collection?(node)
131
+ node.receiver.each_node(:any_block).any?(&:keywords?)
132
+ end
95
133
  end
96
134
  end
97
135
  end
@@ -69,9 +69,13 @@ module RuboCop
69
69
 
70
70
  # @!method variable_argument?(node)
71
71
  def_node_matcher :variable_argument?, <<~PATTERN
72
- (send {str dstr} :% #autocorrectable?)
72
+ (send {str dstr} :% !#autocorrectable?)
73
73
  PATTERN
74
74
 
75
+ def on_new_investigation
76
+ @corrected_ranges = nil
77
+ end
78
+
75
79
  def on_send(node)
76
80
  formatter(node) do |selector|
77
81
  detected_style = selector == :% ? :percent : selector
@@ -86,10 +90,12 @@ module RuboCop
86
90
 
87
91
  private
88
92
 
93
+ # Only an argument that is provably not an array can be corrected,
94
+ # because `String#%` spreads an array argument and `format` does not.
89
95
  def autocorrectable?(node)
90
- return true if node.lvar_type?
96
+ return true if node.literal?
91
97
 
92
- node.send_type? && !AUTOCORRECTABLE_METHODS.include?(node.method_name)
98
+ node.call_type? && AUTOCORRECTABLE_METHODS.include?(node.method_name)
93
99
  end
94
100
 
95
101
  def message(detected_style)
@@ -101,7 +107,9 @@ module RuboCop
101
107
  end
102
108
 
103
109
  def autocorrect(corrector, node)
104
- return if variable_argument?(node)
110
+ return if variable_argument?(node) || within_corrected_range?(node)
111
+
112
+ corrected_ranges << node.source_range
105
113
 
106
114
  case node.method_name
107
115
  when :%
@@ -116,6 +124,14 @@ module RuboCop
116
124
  end
117
125
  end
118
126
 
127
+ def within_corrected_range?(node)
128
+ corrected_ranges.any? { |range| range.contains?(node.source_range) }
129
+ end
130
+
131
+ def corrected_ranges
132
+ @corrected_ranges ||= []
133
+ end
134
+
119
135
  def autocorrect_from_percent(corrector, node)
120
136
  percent_rhs = node.first_argument
121
137
  args = case percent_rhs.type
@@ -146,14 +162,20 @@ module RuboCop
146
162
  def format_single_parameter(arg)
147
163
  # `format(fmt, *args)` is equivalent to `fmt % args`, so unwrap the splat
148
164
  # and render the argument it splats.
149
- return format_single_parameter(arg.children.first) if arg.splat_type?
165
+ return format_splatted_parameter(arg.children.first) if arg.splat_type?
150
166
 
151
167
  source = arg.source
152
- return "{ #{source} }" if arg.hash_type?
168
+ return "{ #{source} }" if arg.hash_type? && !arg.braces?
153
169
 
154
170
  requires_parentheses?(arg) ? "(#{source})" : source
155
171
  end
156
172
 
173
+ def format_splatted_parameter(arg)
174
+ return "#{arg.source}.to_a" if arg.hash_type?
175
+
176
+ format_single_parameter(arg)
177
+ end
178
+
157
179
  # An argument that binds looser than `%` (a ternary, range, assignment, or
158
180
  # operator call) must be parenthesized to keep its meaning.
159
181
  def requires_parentheses?(arg)
@@ -142,7 +142,8 @@ module RuboCop
142
142
  end
143
143
 
144
144
  next_token = processed_source.tokens[token_number]
145
- if next_token&.text&.valid_encoding? && Encoding::ENCODING_PATTERN.match?(next_token.text)
145
+ if next_token&.text&.valid_encoding? &&
146
+ MagicComment.parse(next_token.text).encoding_specified?
146
147
  token = next_token
147
148
  end
148
149
 
@@ -282,7 +282,15 @@ module RuboCop
282
282
  end
283
283
 
284
284
  def accepted_form?(node, ending: false)
285
- accepted_if?(node, ending) || node.condition.multiline? || node.parent&.assignment?
285
+ accepted_if?(node, ending) || node.condition.multiline? ||
286
+ node.parent&.assignment? || endless_method_body?(node)
287
+ end
288
+
289
+ # The body of an endless method is a single expression, so there is no
290
+ # room for a guard clause followed by the rest of the code.
291
+ def endless_method_body?(node)
292
+ parent = node.parent
293
+ parent&.any_def_type? && parent.endless?
286
294
  end
287
295
 
288
296
  def trivial?(node)
@@ -53,6 +53,12 @@ module RuboCop
53
53
  # @!method hash_from_array?(node)
54
54
  def_node_matcher :hash_from_array?, '(send (const {nil? cbase} :Hash) :[] ...)'
55
55
 
56
+ # A hash literal as the first argument needs the parentheses of the call,
57
+ # which `Style/MethodCallWithArgsParentheses` removes in the same pass.
58
+ def self.autocorrect_incompatible_with
59
+ [Style::MethodCallWithArgsParentheses]
60
+ end
61
+
56
62
  def on_send(node)
57
63
  return if part_of_ignored_node?(node) || !hash_from_array?(node)
58
64
 
@@ -95,12 +101,30 @@ module RuboCop
95
101
  def register_offense_for_hash(node, hash_argument)
96
102
  add_offense(node, message: MSG_LITERAL_HASH_ARG) do |corrector|
97
103
  corrector.replace(node, "{#{hash_argument.source}}")
98
-
99
- parent = node.parent
100
- add_parentheses(parent, corrector) if parent&.send_type? && !parent.parenthesized?
104
+ add_parentheses_to_parent_call(node, corrector)
101
105
  end
102
106
  end
103
107
 
108
+ # Without parentheses, a hash literal at the start of the first argument would be
109
+ # parsed as a block. That includes an argument like `Hash[a, b].merge(c)`.
110
+ def add_parentheses_to_parent_call(node, corrector)
111
+ node = node.parent while starts_parent?(node)
112
+ parent = node.parent
113
+ return unless parent&.type?(:call, :super, :yield)
114
+ return if parent.parenthesized? || operator_syntax?(parent)
115
+
116
+ add_parentheses(parent, corrector)
117
+ end
118
+
119
+ def starts_parent?(node)
120
+ node.parent && node.parent.source_range.begin_pos == node.source_range.begin_pos
121
+ end
122
+
123
+ # `foo[x]`, `foo.bar = x` and `foo == x` take a hash literal as it is.
124
+ def operator_syntax?(call)
125
+ call.setter_method? || (call.operator_method? && !call.loc?(:dot))
126
+ end
127
+
104
128
  def register_offense_for_zip_method(node, zip_method)
105
129
  add_offense(node, message: MSG_TO_H) do |corrector|
106
130
  if zip_method.parenthesized?
@@ -135,11 +159,7 @@ module RuboCop
135
159
  def correct_multi_argument(node)
136
160
  add_offense(node, message: MSG_LITERAL_MULTI_ARG) do |corrector|
137
161
  corrector.replace(node, args_to_hash(node.arguments))
138
-
139
- parent = node.parent
140
- if parent&.send_type? && !parent.method?(:to_h) && !parent.parenthesized?
141
- add_parentheses(parent, corrector)
142
- end
162
+ add_parentheses_to_parent_call(node, corrector)
143
163
  end
144
164
  end
145
165
 
@@ -34,6 +34,9 @@ module RuboCop
34
34
  # # good - fetch with block is allowed
35
35
  # hash.fetch(key) { default }
36
36
  #
37
+ # # good - `ENV.fetch` is left to `Style/FetchEnvVar` when it is enabled
38
+ # ENV.fetch(key)
39
+ #
37
40
  # @example EnforcedStyle: fetch
38
41
  # # bad
39
42
  # hash[key]
@@ -55,6 +58,9 @@ module RuboCop
55
58
 
56
59
  RESTRICT_ON_SEND = %i[[] fetch].freeze
57
60
 
61
+ # @!method env_const?(node)
62
+ def_node_matcher :env_const?, '(const {nil? cbase} :ENV)'
63
+
58
64
  def on_send(node)
59
65
  return if (receiver = node.receiver) && allowed_receiver?(receiver)
60
66
  return if part_of_ignored_node?(node)
@@ -76,28 +82,45 @@ module RuboCop
76
82
  private
77
83
 
78
84
  def offense_for_brackets?(node)
79
- style == :brackets && node.receiver && node.method?(:fetch) && node.arguments.one? &&
80
- !node.block_literal? && !node.csend_type?
85
+ return false if style != :brackets || !node.receiver
86
+ return false if !node.method?(:fetch) || !node.arguments.one?
87
+ return false if node.block_literal? || node.csend_type?
88
+
89
+ !exempt_from_brackets?(node)
90
+ end
91
+
92
+ def exempt_from_brackets?(node)
93
+ comment_before_dot?(node) || env_fetch_preferred?(node.receiver)
94
+ end
95
+
96
+ def comment_before_dot?(node)
97
+ processed_source.contains_comment?(node.receiver.source_range.end.join(node.loc.dot))
98
+ end
99
+
100
+ def env_fetch_preferred?(receiver)
101
+ env_const?(receiver) && processed_source.registry.enabled?(Style::FetchEnvVar, config)
81
102
  end
82
103
 
83
104
  def offense_for_fetch?(node)
84
- style == :fetch && node.method?(:[]) && node.arguments.one?
105
+ style == :fetch && node.method?(:[]) && node.arguments.one? &&
106
+ !compound_assignment_target?(node)
107
+ end
108
+
109
+ def compound_assignment_target?(node)
110
+ node.parent&.type?(:op_asgn, :or_asgn, :and_asgn) && node.parent.children.first == node
85
111
  end
86
112
 
87
113
  def correct_fetch_to_brackets(corrector, node)
88
114
  key = node.first_argument.source
89
115
 
90
- corrector.replace(node.loc.dot.join(node.source_range.end), "[#{key}]")
116
+ corrector.replace(node.receiver.source_range.end.join(node.source_range.end), "[#{key}]")
91
117
  end
92
118
 
93
119
  def correct_brackets_to_fetch(corrector, node)
94
120
  key = node.first_argument.source
121
+ replacement = node.loc.dot ? "fetch(#{key})" : ".fetch(#{key})"
95
122
 
96
- if node.csend_type?
97
- corrector.replace(node.loc.dot.join(node.source_range.end), "&.fetch(#{key})")
98
- else
99
- corrector.replace(node.loc.selector.join(node.source_range.end), ".fetch(#{key})")
100
- end
123
+ corrector.replace(node.loc.selector.join(node.source_range.end), replacement)
101
124
  end
102
125
  end
103
126
  end
@@ -142,6 +142,13 @@ module RuboCop
142
142
  MSG_HASH_ROCKETS = 'Use hash rockets syntax.'
143
143
  NO_MIXED_KEYS_STYLES = %i[ruby19_no_mixed_keys no_mixed_keys].freeze
144
144
 
145
+ # Omitting the last hash value of a call without parentheses adds the parentheses,
146
+ # which `Style/MethodCallWithArgsParentheses` and `Style/SuperWithArgsParentheses`
147
+ # would add again in the same pass.
148
+ def self.autocorrect_incompatible_with
149
+ [Style::MethodCallWithArgsParentheses, Style::SuperWithArgsParentheses]
150
+ end
151
+
145
152
  def on_hash(node)
146
153
  pairs = node.pairs
147
154
 
@@ -241,6 +248,8 @@ module RuboCop
241
248
  def check(pairs, delim, msg)
242
249
  pairs.each do |pair|
243
250
  if pair.delimiter == delim
251
+ next if pair.value_omission? && keyword_value?(pair)
252
+
244
253
  location = pair.source_range.begin.join(pair.loc.operator)
245
254
  add_offense(location, message: msg) do |corrector|
246
255
  autocorrect(corrector, pair)
@@ -283,12 +292,16 @@ module RuboCop
283
292
  end
284
293
 
285
294
  def autocorrect_hash_rockets(corrector, pair_node)
286
- op = pair_node.loc.operator
287
-
288
295
  key_with_hash_rocket = ":#{pair_node.key.source}#{pair_node.inverse_delimiter(true)}"
289
296
  key_with_hash_rocket += pair_node.key.source if pair_node.value_omission?
290
297
  corrector.replace(pair_node.key, key_with_hash_rocket)
291
- corrector.remove(range_with_surrounding_space(op))
298
+ corrector.remove(operator_range(pair_node))
299
+ end
300
+
301
+ def operator_range(pair_node)
302
+ op = pair_node.loc.operator
303
+
304
+ range_with_surrounding_space(op, newlines: !pair_node.value_omission?)
292
305
  end
293
306
 
294
307
  def autocorrect_no_mixed_keys(corrector, pair_node)
@@ -188,7 +188,7 @@ module RuboCop
188
188
  end
189
189
  end
190
190
 
191
- # rubocop:disable-next Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
191
+ # rubocop:disable-next Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
192
192
  def check_expressions(node, expressions, insert_position)
193
193
  return if expressions.any?(&:nil?)
194
194
 
@@ -198,8 +198,7 @@ module RuboCop
198
198
  add_offense(expression) do |corrector|
199
199
  next if node.if_type? && (node.ternary? || node.then?)
200
200
 
201
- range = range_by_whole_lines(expression.source_range, include_final_newline: true)
202
- corrector.remove(range)
201
+ corrector.remove(removal_range(expression))
203
202
  next if inserted_expression
204
203
 
205
204
  if node.parent&.assignment?
@@ -213,6 +212,26 @@ module RuboCop
213
212
  end
214
213
  end
215
214
 
215
+ def removal_range(expression)
216
+ range = expression.source_range
217
+ whole_lines = range_by_whole_lines(range, include_final_newline: true)
218
+ return whole_lines if whole_lines.source.strip == expression.source
219
+
220
+ range_with_separator(range)
221
+ end
222
+
223
+ def range_with_separator(range)
224
+ source = range.source_buffer.source
225
+
226
+ if (following = source[range.end_pos..][/\A[ \t]*;[ \t]*/])
227
+ range.adjust(end_pos: following.length)
228
+ elsif (preceding = source[0...range.begin_pos][/[ \t]*;[ \t]*\z/])
229
+ range.adjust(begin_pos: -preceding.length)
230
+ else
231
+ range_with_surrounding_space(range, side: :right, newlines: false)
232
+ end
233
+ end
234
+
216
235
  def correct_assignment(corrector, node, expression, insert_position)
217
236
  indentation = indentation_of(node.parent)
218
237
 
@@ -75,7 +75,7 @@ module RuboCop
75
75
  return if comments_between_else_and_if?(node, else_branch)
76
76
 
77
77
  add_offense(else_branch.loc.keyword) do |corrector|
78
- next if part_of_ignored_node?(node)
78
+ next if part_of_ignored_node?(node) || shares_lines_with_other_code?(else_branch)
79
79
 
80
80
  autocorrect(corrector, else_branch)
81
81
  ignore_node(node)
@@ -84,6 +84,26 @@ module RuboCop
84
84
 
85
85
  private
86
86
 
87
+ # The `elsif` correction moves and removes whole lines, so it can't be applied when
88
+ # the nested `if` condition, its `if` branch or its `end` shares a line with other code.
89
+ # A modifier `if` on the `else` line would leave its body on the `elsif` line.
90
+ def shares_lines_with_other_code?(node)
91
+ return false if node.then?
92
+ return !begins_its_line?(node.source_range) if node.modifier_form?
93
+
94
+ moved_ranges(node).any? { |range| !begins_its_line?(range) || !ends_its_line?(range) }
95
+ end
96
+
97
+ def moved_ranges(node)
98
+ ranges = [if_condition_range(node, node.condition), find_end_range(node)]
99
+ ranges << range_with_comments(node.if_branch) if node.if_branch
100
+ ranges
101
+ end
102
+
103
+ def ends_its_line?(range)
104
+ processed_source.lines[range.last_line - 1][range.last_column..].match?(/\A\s*(#|\z)/)
105
+ end
106
+
87
107
  def autocorrect(corrector, node)
88
108
  if node.then?
89
109
  # If the nested `if` is a then node, correct it first,
@@ -159,7 +159,7 @@ module RuboCop
159
159
  corrector.replace(node, replacement)
160
160
  end
161
161
 
162
- def replacement_for_modifier_form(corrector, node) # rubocop:disable Metrics/AbcSize
162
+ def replacement_for_modifier_form(corrector, node)
163
163
  comment = comment_on_node_line(node)
164
164
  if comment && too_long_due_to_comment_after_modifier?(node, comment)
165
165
  remove_comment(corrector, node, comment)
@@ -167,12 +167,11 @@ module RuboCop
167
167
  return to_modifier_form_with_move_comment(node, indent(node), comment)
168
168
  end
169
169
 
170
- last_argument = node.if_branch.last_argument if node.if_branch.send_type?
171
- if last_argument.respond_to?(:heredoc?) && last_argument.heredoc?
172
- heredoc = extract_heredoc_from(last_argument)
173
- remove_heredoc(corrector, heredoc)
170
+ heredoc_ranges = heredoc_ranges_in(node.if_branch)
171
+ unless heredoc_ranges.empty?
172
+ remove_heredoc(corrector, heredoc_ranges)
174
173
 
175
- return to_normal_form_with_heredoc(node, indent(node), heredoc)
174
+ return to_normal_form_with_heredoc(node, indent(node), heredoc_ranges)
176
175
  end
177
176
 
178
177
  to_normal_form(node, indent(node))
@@ -275,14 +274,19 @@ module RuboCop
275
274
  RUBY
276
275
  end
277
276
 
278
- def to_normal_form_with_heredoc(node, indentation, heredoc)
279
- heredoc_body, heredoc_end = heredoc
277
+ def remove_heredoc(corrector, heredoc_ranges)
278
+ heredoc_ranges.each do |range|
279
+ corrector.remove(range_by_whole_lines(range, include_final_newline: true))
280
+ end
281
+ end
282
+
283
+ def to_normal_form_with_heredoc(node, indentation, heredoc_ranges)
284
+ heredoc_lines = heredoc_ranges.map(&:source)
280
285
 
281
286
  <<~RUBY.chomp
282
287
  #{node.keyword} #{node.condition.source}
283
288
  #{indentation} #{node.body.source}
284
- #{indentation} #{heredoc_body.source.chomp}
285
- #{indentation} #{heredoc_end.source.chomp}
289
+ #{heredoc_lines.join("\n")}
286
290
  #{indentation}end
287
291
  RUBY
288
292
  end
@@ -294,17 +298,12 @@ module RuboCop
294
298
  RUBY
295
299
  end
296
300
 
297
- def extract_heredoc_from(last_argument)
298
- heredoc_body = last_argument.loc.heredoc_body
299
- heredoc_end = last_argument.loc.heredoc_end
300
-
301
- [heredoc_body, heredoc_end]
302
- end
303
-
304
- def remove_heredoc(corrector, heredoc)
305
- heredoc.each do |range|
306
- corrector.remove(range_by_whole_lines(range, include_final_newline: true))
301
+ def heredoc_ranges_in(body)
302
+ ranges = body.each_node(:any_str).select(&:heredoc?).map do |heredoc|
303
+ range_by_whole_lines(heredoc.loc.heredoc_body.join(heredoc.loc.heredoc_end))
307
304
  end
305
+ # A heredoc opened in another heredoc's interpolation is moved along with it.
306
+ ranges.reject { |range| ranges.any? { |other| other.contains?(range) } }
308
307
  end
309
308
 
310
309
  def comment_on_node_line(node)
@@ -28,6 +28,10 @@ module RuboCop
28
28
 
29
29
  MSG = 'Avoid modifier `%<keyword>s` after another conditional.'
30
30
 
31
+ def self.autocorrect_incompatible_with
32
+ [Style::SoleNestedConditional]
33
+ end
34
+
31
35
  # rubocop:disable-next Metrics/AbcSize
32
36
  def on_if(node)
33
37
  return unless node.modifier_form? && node.body.if_type?