rubocop 1.87.0 → 1.88.2

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 (193) hide show
  1. checksums.yaml +4 -4
  2. data/config/default.yml +80 -72
  3. data/config/obsoletion.yml +21 -1
  4. data/lib/rubocop/cli/command/auto_generate_config.rb +6 -0
  5. data/lib/rubocop/cop/base.rb +30 -10
  6. data/lib/rubocop/cop/bundler/gem_comment.rb +5 -3
  7. data/lib/rubocop/cop/commissioner.rb +13 -11
  8. data/lib/rubocop/cop/correctors/each_to_for_corrector.rb +1 -1
  9. data/lib/rubocop/cop/correctors/lambda_literal_to_method_corrector.rb +7 -1
  10. data/lib/rubocop/cop/correctors/ordered_gem_corrector.rb +8 -1
  11. data/lib/rubocop/cop/gemspec/development_dependencies.rb +1 -1
  12. data/lib/rubocop/cop/gemspec/duplicated_assignment.rb +12 -2
  13. data/lib/rubocop/cop/gemspec/require_mfa.rb +4 -1
  14. data/lib/rubocop/cop/internal_affairs/redundant_let_rubocop_config_new.rb +5 -3
  15. data/lib/rubocop/cop/layout/block_alignment.rb +58 -4
  16. data/lib/rubocop/cop/layout/class_structure.rb +7 -3
  17. data/lib/rubocop/cop/layout/comment_indentation.rb +9 -2
  18. data/lib/rubocop/cop/layout/condition_position.rb +13 -3
  19. data/lib/rubocop/cop/layout/else_alignment.rb +1 -14
  20. data/lib/rubocop/cop/layout/empty_comment.rb +8 -10
  21. data/lib/rubocop/cop/layout/empty_line_between_defs.rb +14 -1
  22. data/lib/rubocop/cop/layout/first_hash_element_indentation.rb +13 -14
  23. data/lib/rubocop/cop/layout/indentation_width.rb +28 -0
  24. data/lib/rubocop/cop/layout/line_length.rb +10 -5
  25. data/lib/rubocop/cop/layout/multiline_method_call_indentation.rb +6 -1
  26. data/lib/rubocop/cop/layout/space_around_operators.rb +6 -2
  27. data/lib/rubocop/cop/lint/ambiguous_assignment.rb +1 -11
  28. data/lib/rubocop/cop/lint/ambiguous_operator_precedence.rb +1 -10
  29. data/lib/rubocop/cop/lint/assignment_in_condition.rb +13 -1
  30. data/lib/rubocop/cop/lint/circular_argument_reference.rb +1 -3
  31. data/lib/rubocop/cop/lint/debugger.rb +13 -2
  32. data/lib/rubocop/cop/lint/deprecated_constants.rb +1 -7
  33. data/lib/rubocop/cop/lint/empty_block.rb +3 -3
  34. data/lib/rubocop/cop/lint/ensure_return.rb +19 -1
  35. data/lib/rubocop/cop/lint/erb_new_arguments.rb +3 -1
  36. data/lib/rubocop/cop/lint/float_comparison.rb +1 -0
  37. data/lib/rubocop/cop/lint/incompatible_io_select_with_fiber_scheduler.rb +5 -1
  38. data/lib/rubocop/cop/lint/interpolation_check.rb +18 -3
  39. data/lib/rubocop/cop/lint/lambda_without_literal_block.rb +1 -1
  40. data/lib/rubocop/cop/lint/literal_assignment_in_condition.rb +11 -1
  41. data/lib/rubocop/cop/lint/literal_in_interpolation.rb +8 -11
  42. data/lib/rubocop/cop/lint/missing_cop_enable_directive.rb +4 -4
  43. data/lib/rubocop/cop/lint/no_return_in_begin_end_blocks.rb +16 -0
  44. data/lib/rubocop/cop/lint/non_local_exit_from_iterator.rb +1 -1
  45. data/lib/rubocop/cop/lint/number_conversion.rb +13 -4
  46. data/lib/rubocop/cop/lint/numeric_operation_with_constant_result.rb +10 -0
  47. data/lib/rubocop/cop/lint/ordered_magic_comments.rb +7 -7
  48. data/lib/rubocop/cop/lint/raise_exception.rb +1 -1
  49. data/lib/rubocop/cop/lint/rand_one.rb +1 -1
  50. data/lib/rubocop/cop/lint/redundant_cop_disable_directive.rb +4 -1
  51. data/lib/rubocop/cop/lint/redundant_cop_enable_directive.rb +4 -1
  52. data/lib/rubocop/cop/lint/redundant_dir_glob_sort.rb +15 -4
  53. data/lib/rubocop/cop/lint/redundant_safe_navigation.rb +14 -7
  54. data/lib/rubocop/cop/lint/redundant_splat_expansion.rb +4 -0
  55. data/lib/rubocop/cop/lint/redundant_type_conversion.rb +7 -0
  56. data/lib/rubocop/cop/lint/redundant_with_index.rb +1 -1
  57. data/lib/rubocop/cop/lint/redundant_with_object.rb +5 -0
  58. data/lib/rubocop/cop/lint/refinement_import_methods.rb +8 -1
  59. data/lib/rubocop/cop/lint/regexp_as_condition.rb +9 -1
  60. data/lib/rubocop/cop/lint/require_parentheses.rb +13 -4
  61. data/lib/rubocop/cop/lint/require_range_parentheses.rb +2 -1
  62. data/lib/rubocop/cop/lint/require_relative_self_path.rb +5 -5
  63. data/lib/rubocop/cop/lint/rescue_type.rb +1 -1
  64. data/lib/rubocop/cop/lint/safe_navigation_chain.rb +1 -0
  65. data/lib/rubocop/cop/lint/safe_navigation_with_empty.rb +1 -1
  66. data/lib/rubocop/cop/lint/script_permission.rb +5 -1
  67. data/lib/rubocop/cop/lint/self_assignment.rb +24 -1
  68. data/lib/rubocop/cop/lint/send_with_mixin_argument.rb +1 -1
  69. data/lib/rubocop/cop/lint/shadowing_outer_local_variable.rb +14 -0
  70. data/lib/rubocop/cop/lint/shared_mutable_default.rb +3 -1
  71. data/lib/rubocop/cop/lint/suppressed_exception_in_number_conversion.rb +12 -0
  72. data/lib/rubocop/cop/lint/symbol_conversion.rb +21 -4
  73. data/lib/rubocop/cop/lint/to_enum_arguments.rb +35 -2
  74. data/lib/rubocop/cop/lint/to_json.rb +8 -1
  75. data/lib/rubocop/cop/lint/top_level_return_with_argument.rb +1 -1
  76. data/lib/rubocop/cop/lint/trailing_comma_in_attribute_declaration.rb +4 -1
  77. data/lib/rubocop/cop/lint/unescaped_bracket_in_regexp.rb +35 -9
  78. data/lib/rubocop/cop/lint/unreachable_code.rb +9 -4
  79. data/lib/rubocop/cop/lint/useless_assignment.rb +10 -5
  80. data/lib/rubocop/cop/lint/useless_numeric_operation.rb +10 -10
  81. data/lib/rubocop/cop/lint/useless_ruby2_keywords.rb +7 -3
  82. data/lib/rubocop/cop/lint/useless_setter_call.rb +4 -1
  83. data/lib/rubocop/cop/lint/useless_times.rb +22 -1
  84. data/lib/rubocop/cop/lint/void.rb +2 -2
  85. data/lib/rubocop/cop/metrics/collection_literal_length.rb +1 -1
  86. data/lib/rubocop/cop/metrics/method_length.rb +1 -1
  87. data/lib/rubocop/cop/metrics/perceived_complexity.rb +38 -7
  88. data/lib/rubocop/cop/mixin/allowed_methods.rb +5 -5
  89. data/lib/rubocop/cop/mixin/allowed_pattern.rb +5 -1
  90. data/lib/rubocop/cop/mixin/forbidden_pattern.rb +5 -1
  91. data/lib/rubocop/cop/mixin/hash_subset.rb +8 -0
  92. data/lib/rubocop/cop/mixin/hash_transform_method.rb +4 -0
  93. data/lib/rubocop/cop/mixin/statement_modifier.rb +1 -1
  94. data/lib/rubocop/cop/naming/file_name.rb +4 -3
  95. data/lib/rubocop/cop/naming/inclusive_language.rb +8 -2
  96. data/lib/rubocop/cop/naming/memoized_instance_variable_name.rb +9 -0
  97. data/lib/rubocop/cop/naming/rescued_exceptions_variable_name.rb +9 -3
  98. data/lib/rubocop/cop/security/io_methods.rb +1 -1
  99. data/lib/rubocop/cop/security/marshal_load.rb +1 -1
  100. data/lib/rubocop/cop/style/accessor_grouping.rb +11 -1
  101. data/lib/rubocop/cop/style/alias.rb +1 -1
  102. data/lib/rubocop/cop/style/and_or.rb +1 -1
  103. data/lib/rubocop/cop/style/arguments_forwarding.rb +1 -1
  104. data/lib/rubocop/cop/style/array_first_last.rb +12 -1
  105. data/lib/rubocop/cop/style/array_intersect.rb +8 -4
  106. data/lib/rubocop/cop/style/array_intersect_with_single_element.rb +3 -0
  107. data/lib/rubocop/cop/style/block_delimiters.rb +16 -2
  108. data/lib/rubocop/cop/style/case_equality.rb +14 -2
  109. data/lib/rubocop/cop/style/class_equality_comparison.rb +21 -13
  110. data/lib/rubocop/cop/style/class_methods_definitions.rb +11 -5
  111. data/lib/rubocop/cop/style/collection_compact.rb +1 -1
  112. data/lib/rubocop/cop/style/colon_method_call.rb +13 -6
  113. data/lib/rubocop/cop/style/combinable_loops.rb +5 -0
  114. data/lib/rubocop/cop/style/comparable_clamp.rb +12 -1
  115. data/lib/rubocop/cop/style/concat_array_literals.rb +5 -1
  116. data/lib/rubocop/cop/style/conditional_assignment.rb +6 -1
  117. data/lib/rubocop/cop/style/constant_visibility.rb +4 -1
  118. data/lib/rubocop/cop/style/data_inheritance.rb +4 -0
  119. data/lib/rubocop/cop/style/date_time.rb +2 -2
  120. data/lib/rubocop/cop/style/def_with_parentheses.rb +6 -2
  121. data/lib/rubocop/cop/style/dig_chain.rb +5 -0
  122. data/lib/rubocop/cop/style/dir_empty.rb +4 -0
  123. data/lib/rubocop/cop/style/documentation_method.rb +5 -1
  124. data/lib/rubocop/cop/style/empty_case_condition.rb +12 -2
  125. data/lib/rubocop/cop/style/empty_class_definition.rb +8 -1
  126. data/lib/rubocop/cop/style/empty_heredoc.rb +4 -0
  127. data/lib/rubocop/cop/style/empty_literal.rb +7 -2
  128. data/lib/rubocop/cop/style/empty_string_inside_interpolation.rb +30 -20
  129. data/lib/rubocop/cop/style/env_home.rb +4 -0
  130. data/lib/rubocop/cop/style/even_odd.rb +11 -1
  131. data/lib/rubocop/cop/style/exact_regexp_match.rb +8 -1
  132. data/lib/rubocop/cop/style/fetch_env_var.rb +1 -1
  133. data/lib/rubocop/cop/style/file_null.rb +4 -2
  134. data/lib/rubocop/cop/style/file_write.rb +17 -14
  135. data/lib/rubocop/cop/style/format_string.rb +13 -1
  136. data/lib/rubocop/cop/style/hash_conversion.rb +14 -6
  137. data/lib/rubocop/cop/style/hash_lookup_method.rb +2 -6
  138. data/lib/rubocop/cop/style/hash_slice.rb +16 -0
  139. data/lib/rubocop/cop/style/hash_syntax.rb +2 -0
  140. data/lib/rubocop/cop/style/identical_conditional_branches.rb +15 -4
  141. data/lib/rubocop/cop/style/if_unless_modifier.rb +5 -5
  142. data/lib/rubocop/cop/style/if_with_semicolon.rb +9 -1
  143. data/lib/rubocop/cop/style/inline_comment.rb +1 -1
  144. data/lib/rubocop/cop/style/invertible_unless_condition.rb +25 -5
  145. data/lib/rubocop/cop/style/keyword_arguments_merging.rb +4 -0
  146. data/lib/rubocop/cop/style/keyword_parameters_order.rb +7 -3
  147. data/lib/rubocop/cop/style/lambda.rb +7 -1
  148. data/lib/rubocop/cop/style/lambda_call.rb +11 -0
  149. data/lib/rubocop/cop/style/map_compact_with_conditional_block.rb +11 -0
  150. data/lib/rubocop/cop/style/map_into_array.rb +1 -1
  151. data/lib/rubocop/cop/style/method_call_without_args_parentheses.rb +11 -5
  152. data/lib/rubocop/cop/style/method_def_parentheses.rb +5 -1
  153. data/lib/rubocop/cop/style/min_max_comparison.rb +3 -0
  154. data/lib/rubocop/cop/style/missing_respond_to_missing.rb +10 -7
  155. data/lib/rubocop/cop/style/mixin_usage.rb +1 -1
  156. data/lib/rubocop/cop/style/module_function.rb +4 -2
  157. data/lib/rubocop/cop/style/multiline_if_then.rb +1 -1
  158. data/lib/rubocop/cop/style/multiline_memoization.rb +7 -1
  159. data/lib/rubocop/cop/style/multiline_method_signature.rb +11 -4
  160. data/lib/rubocop/cop/style/mutable_constant.rb +105 -11
  161. data/lib/rubocop/cop/style/nil_lambda.rb +8 -0
  162. data/lib/rubocop/cop/style/numeric_predicate.rb +1 -1
  163. data/lib/rubocop/cop/style/open_struct_use.rb +1 -1
  164. data/lib/rubocop/cop/style/option_hash.rb +1 -1
  165. data/lib/rubocop/cop/style/optional_arguments.rb +1 -0
  166. data/lib/rubocop/cop/style/parallel_assignment.rb +19 -3
  167. data/lib/rubocop/cop/style/percent_literal_delimiters.rb +2 -0
  168. data/lib/rubocop/cop/style/perl_backrefs.rb +5 -3
  169. data/lib/rubocop/cop/style/redundant_current_directory_in_path.rb +13 -1
  170. data/lib/rubocop/cop/style/redundant_exception.rb +6 -0
  171. data/lib/rubocop/cop/style/redundant_filter_chain.rb +1 -1
  172. data/lib/rubocop/cop/style/redundant_format.rb +29 -0
  173. data/lib/rubocop/cop/style/redundant_heredoc_delimiter_quotes.rb +6 -1
  174. data/lib/rubocop/cop/style/redundant_line_continuation.rb +11 -3
  175. data/lib/rubocop/cop/style/redundant_regexp_escape.rb +8 -4
  176. data/lib/rubocop/cop/style/redundant_self.rb +9 -0
  177. data/lib/rubocop/cop/style/redundant_struct_keyword_init.rb +23 -4
  178. data/lib/rubocop/cop/style/semicolon.rb +20 -5
  179. data/lib/rubocop/cop/style/single_line_do_end_block.rb +17 -4
  180. data/lib/rubocop/cop/style/string_hash_keys.rb +1 -0
  181. data/lib/rubocop/cop/style/struct_inheritance.rb +3 -1
  182. data/lib/rubocop/cop/style/ternary_parentheses.rb +11 -0
  183. data/lib/rubocop/cop/style/trailing_underscore_variable.rb +7 -8
  184. data/lib/rubocop/cop/style/trivial_accessors.rb +1 -1
  185. data/lib/rubocop/cop/style/while_until_do.rb +7 -0
  186. data/lib/rubocop/cop/style/word_array.rb +1 -0
  187. data/lib/rubocop/cop/style/zero_length_predicate.rb +6 -3
  188. data/lib/rubocop/cop/team.rb +18 -4
  189. data/lib/rubocop/formatter/disabled_config_formatter.rb +14 -7
  190. data/lib/rubocop/runner.rb +55 -13
  191. data/lib/rubocop/server/core.rb +6 -0
  192. data/lib/rubocop/version.rb +1 -1
  193. metadata +3 -3
@@ -121,16 +121,24 @@ module RuboCop
121
121
  end
122
122
 
123
123
  def multi_argument(node)
124
+ # A splat argument can expand to any number of elements, so the pairs
125
+ # can't be built statically and there is no literal hash to suggest.
126
+ return if node.arguments.any?(&:splat_type?)
127
+
124
128
  if node.arguments.count.odd?
125
129
  add_offense(node, message: MSG_LITERAL_MULTI_ARG)
126
130
  else
127
- add_offense(node, message: MSG_LITERAL_MULTI_ARG) do |corrector|
128
- corrector.replace(node, args_to_hash(node.arguments))
131
+ correct_multi_argument(node)
132
+ end
133
+ end
129
134
 
130
- parent = node.parent
131
- if parent&.send_type? && !parent.method?(:to_h) && !parent.parenthesized?
132
- add_parentheses(parent, corrector)
133
- end
135
+ def correct_multi_argument(node)
136
+ add_offense(node, message: MSG_LITERAL_MULTI_ARG) do |corrector|
137
+ 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)
134
142
  end
135
143
  end
136
144
  end
@@ -74,7 +74,7 @@ module RuboCop
74
74
 
75
75
  def offense_for_brackets?(node)
76
76
  style == :brackets && node.receiver && node.method?(:fetch) && node.arguments.one? &&
77
- !node.block_literal?
77
+ !node.block_literal? && !node.csend_type?
78
78
  end
79
79
 
80
80
  def offense_for_fetch?(node)
@@ -84,11 +84,7 @@ module RuboCop
84
84
  def correct_fetch_to_brackets(corrector, node)
85
85
  key = node.first_argument.source
86
86
 
87
- if node.csend_type?
88
- corrector.replace(node, "(#{node.receiver.source}[#{key}])")
89
- else
90
- corrector.replace(node.loc.dot.join(node.source_range.end), "[#{key}]")
91
- end
87
+ corrector.replace(node.loc.dot.join(node.source_range.end), "[#{key}]")
92
88
  end
93
89
 
94
90
  def correct_brackets_to_fetch(corrector, node)
@@ -19,6 +19,22 @@ module RuboCop
19
19
  # This cop is unsafe because it cannot be guaranteed that the receiver
20
20
  # is a `Hash` or responds to the replacement method.
21
21
  #
22
+ # Additionally, the replacement may change the order of the resulting
23
+ # hash: `Hash#slice` returns entries in the order the keys are given,
24
+ # whereas `select`, `filter`, and `reject` preserve the entry order of
25
+ # the receiver.
26
+ #
27
+ # For example:
28
+ #
29
+ # [source,ruby]
30
+ # ----
31
+ # hash = {foo: 1, bar: 2, baz: 3}
32
+ # keys = %i[baz foo]
33
+ #
34
+ # hash.select { |k, _v| keys.include?(k) } # => {foo: 1, baz: 3}
35
+ # hash.slice(*keys) # => {baz: 3, foo: 1}
36
+ # ----
37
+ #
22
38
  # @example
23
39
  #
24
40
  # # bad
@@ -263,6 +263,8 @@ module RuboCop
263
263
 
264
264
  hash_node = pair_node.parent
265
265
  return unless hash_node.parent&.return_type? && !hash_node.braces?
266
+ # This runs once per pair, but the hash must only be wrapped once.
267
+ return unless pair_node.equal?(hash_node.pairs.first)
266
268
 
267
269
  corrector.wrap(hash_node, '{', '}')
268
270
  end
@@ -216,23 +216,34 @@ module RuboCop
216
216
  # rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
217
217
 
218
218
  def correct_assignment(corrector, node, expression, insert_position)
219
+ indentation = indentation_of(node.parent)
220
+
219
221
  if insert_position == :after_condition
220
222
  assignment = node.parent.source_range.with(end_pos: node.source_range.begin_pos)
221
223
  corrector.remove(assignment)
222
- corrector.insert_after(node, "\n#{assignment.source}#{expression.source}")
224
+ corrector.insert_after(node, "\n#{indentation}#{assignment.source}#{expression.source}")
223
225
  else
224
- corrector.insert_before(node.parent, "#{expression.source}\n")
226
+ corrector.insert_before(node.parent, "#{expression.source}\n#{indentation}")
225
227
  end
226
228
  end
227
229
 
228
230
  def correct_no_assignment(corrector, node, expression, insert_position)
231
+ indentation = indentation_of(node)
232
+
229
233
  if insert_position == :after_condition
230
- corrector.insert_after(node, "\n#{expression.source}")
234
+ corrector.insert_after(node, "\n#{indentation}#{expression.source}")
231
235
  else
232
- corrector.insert_before(node, "#{expression.source}\n")
236
+ corrector.insert_before(node, "#{expression.source}\n#{indentation}")
233
237
  end
234
238
  end
235
239
 
240
+ # The leading indentation of the line the conditional starts on, so a
241
+ # hoisted expression keeps the surrounding nesting instead of landing
242
+ # at column zero.
243
+ def indentation_of(node)
244
+ ' ' * node.source_range.column
245
+ end
246
+
236
247
  def last_child_of_parent?(node)
237
248
  return true unless (parent = node.parent)
238
249
 
@@ -86,12 +86,12 @@ module RuboCop
86
86
  MSG_USE_NORMAL = 'Modifier form of `%<keyword>s` makes the line too long.'
87
87
 
88
88
  def self.autocorrect_incompatible_with
89
- [Style::SoleNestedConditional]
89
+ [Style::Next, Style::SoleNestedConditional]
90
90
  end
91
91
 
92
92
  # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
93
93
  def on_if(node)
94
- return if endless_method?(node.body) || node.ancestors.any?(&:dstr_type?)
94
+ return if endless_method?(node.body) || node.each_ancestor(:dstr).any?
95
95
 
96
96
  condition = node.condition
97
97
  return if defined_nodes(condition).any? { |n| defined_argument_is_undefined?(node, n) } ||
@@ -119,7 +119,7 @@ module RuboCop
119
119
  if condition.defined_type?
120
120
  [condition]
121
121
  else
122
- condition.each_descendant.select(&:defined_type?)
122
+ condition.each_descendant(:defined?)
123
123
  end
124
124
  end
125
125
 
@@ -137,7 +137,7 @@ module RuboCop
137
137
  if condition.any_match_pattern_type?
138
138
  [condition]
139
139
  else
140
- condition.each_descendant.select(&:any_match_pattern_type?)
140
+ condition.each_descendant(:any_match_pattern)
141
141
  end
142
142
  end
143
143
 
@@ -350,7 +350,7 @@ module RuboCop
350
350
  end
351
351
 
352
352
  def comment_on_node_line(node)
353
- processed_source.comments.find { |c| same_line?(c, node) }
353
+ processed_source.comment_at_line(node.first_line)
354
354
  end
355
355
 
356
356
  def remove_comment(corrector, _node, comment)
@@ -82,7 +82,7 @@ module RuboCop
82
82
 
83
83
  then_code, else_code = else_code, then_code if node.unless?
84
84
 
85
- "#{node.condition.source} ? #{then_code} : #{else_code}"
85
+ "#{ternary_condition(node)} ? #{then_code} : #{else_code}"
86
86
  end
87
87
 
88
88
  def correct_elsif(node)
@@ -103,6 +103,14 @@ module RuboCop
103
103
  "#{method.source}(#{arguments.source})"
104
104
  end
105
105
 
106
+ # An assignment used as the condition must be parenthesized, otherwise the
107
+ # assignment would capture the whole ternary (`a = b ? c : d` instead of
108
+ # `(a = b) ? c : d`), changing what gets assigned.
109
+ def ternary_condition(node)
110
+ condition = node.condition
111
+ condition.assignment? ? "(#{condition.source})" : condition.source
112
+ end
113
+
106
114
  def build_else_branch(second_condition)
107
115
  result = <<~RUBY
108
116
  elsif #{second_condition.condition.source}
@@ -26,7 +26,7 @@ module RuboCop
26
26
  def on_new_investigation
27
27
  processed_source.comments.each do |comment|
28
28
  next if comment_line?(processed_source[comment.loc.line - 1]) ||
29
- comment.text.match?(/\A# rubocop:(enable|disable)/)
29
+ comment.text.match?(/\A# rubocop:(enable|disable|todo)/)
30
30
 
31
31
  add_offense(comment)
32
32
  end
@@ -75,7 +75,9 @@ module RuboCop
75
75
  def invertible?(node) # rubocop:disable Metrics/CyclomaticComplexity
76
76
  case node&.type
77
77
  when :begin
78
- invertible?(node.children.first)
78
+ # A multi-statement `begin` evaluates to its last expression, so it
79
+ # cannot be inverted by negating a single child.
80
+ node.children.one? && invertible?(node.children.first)
79
81
  when :send
80
82
  return false if inheritance_check?(node)
81
83
 
@@ -124,12 +126,17 @@ module RuboCop
124
126
  end
125
127
 
126
128
  def preferred_logical_condition(node)
127
- preferred_lhs = preferred_condition(node.lhs)
128
- preferred_rhs = preferred_condition(node.rhs)
129
+ preferred_lhs = preferred_operand(node, node.lhs)
130
+ preferred_rhs = preferred_operand(node, node.rhs)
129
131
 
130
132
  "#{preferred_lhs} #{node.inverse_operator} #{preferred_rhs}"
131
133
  end
132
134
 
135
+ def preferred_operand(node, operand)
136
+ preferred = preferred_condition(operand)
137
+ parenthesize_inverted_operand?(node, operand) ? "(#{preferred})" : preferred
138
+ end
139
+
133
140
  def autocorrect(corrector, node)
134
141
  case node.type
135
142
  when :begin
@@ -138,11 +145,24 @@ module RuboCop
138
145
  autocorrect_send_node(corrector, node)
139
146
  when :or, :and
140
147
  corrector.replace(node.loc.operator, node.inverse_operator)
141
- autocorrect(corrector, node.lhs)
142
- autocorrect(corrector, node.rhs)
148
+ autocorrect_operand(corrector, node, node.lhs)
149
+ autocorrect_operand(corrector, node, node.rhs)
143
150
  end
144
151
  end
145
152
 
153
+ def autocorrect_operand(corrector, node, operand)
154
+ autocorrect(corrector, operand)
155
+ corrector.wrap(operand, '(', ')') if parenthesize_inverted_operand?(node, operand)
156
+ end
157
+
158
+ # When an `and` is nested in an `or`, inverting both operators turns the
159
+ # `and` into an `or` that now binds looser than its parent, so it must be
160
+ # parenthesized to keep the original grouping (`a && b || c` inverts to
161
+ # `(!a || !b) && !c`, not `!a || !b && !c`).
162
+ def parenthesize_inverted_operand?(node, operand)
163
+ node.or_type? && operand.and_type?
164
+ end
165
+
146
166
  def autocorrect_send_node(corrector, node)
147
167
  if node.method?(:!)
148
168
  corrector.remove(node.loc.selector)
@@ -37,6 +37,10 @@ module RuboCop
37
37
  return unless (ancestor = node.parent&.parent)
38
38
 
39
39
  merge_kwargs?(ancestor) do |merge_node, hash_node, other_hash_node|
40
+ # A block-pass argument (e.g. `merge(other, &block)`) has no keyword
41
+ # equivalent, so spreading it would produce invalid Ruby (`**&block`).
42
+ next if other_hash_node.any?(&:block_pass_type?)
43
+
40
44
  add_offense(merge_node) do |corrector|
41
45
  autocorrect(corrector, node, hash_node, other_hash_node)
42
46
  end
@@ -62,11 +62,15 @@ module RuboCop
62
62
  end
63
63
 
64
64
  def append_newline_to_last_kwoptarg(arguments, corrector)
65
- last_argument = arguments.last
66
- return if last_argument.type?(:kwrestarg, :blockarg)
65
+ # The newline only needs restoring when the moved keyword argument was
66
+ # the last parameter, so removing it also consumes the line break before
67
+ # the body. When a `kwoptarg` already trails the list, the body stays
68
+ # separated and inserting a newline would leave a spurious blank line.
69
+ return unless arguments.last.kwarg_type?
70
+ return if arguments.parent.block_type?
67
71
 
68
72
  last_kwoptarg = arguments.reverse.find(&:kwoptarg_type?)
69
- corrector.insert_after(last_kwoptarg, "\n") unless arguments.parent.block_type?
73
+ corrector.insert_after(last_kwoptarg, "\n")
70
74
  end
71
75
 
72
76
  def remove_kwargs(kwarg_nodes, corrector)
@@ -118,7 +118,13 @@ module RuboCop
118
118
  end
119
119
 
120
120
  def lambda_arg_string(args)
121
- args.children.map(&:source).join(', ')
121
+ # Block-local (shadow) arguments are separated from regular arguments by a
122
+ # `;`; joining everything with `,` would turn them into extra parameters
123
+ # and change the lambda's arity.
124
+ regular, shadow = args.children.partition { |arg| !arg.shadowarg_type? }
125
+ arg_string = regular.map(&:source).join(', ')
126
+ arg_string += "; #{shadow.map(&:source).join(', ')}" unless shadow.empty?
127
+ arg_string
122
128
  end
123
129
  end
124
130
  end
@@ -27,6 +27,9 @@ module RuboCop
27
27
 
28
28
  def on_send(node)
29
29
  return unless node.receiver
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)
30
33
 
31
34
  if offense?(node)
32
35
  prefer = prefer(node)
@@ -48,6 +51,14 @@ module RuboCop
48
51
 
49
52
  private
50
53
 
54
+ def comments_in_node?(node)
55
+ range = node.source_range
56
+ processed_source.comments.any? do |comment|
57
+ range.begin_pos <= comment.source_range.begin_pos &&
58
+ comment.source_range.end_pos <= range.end_pos
59
+ end
60
+ end
61
+
51
62
  def offense?(node)
52
63
  (explicit_style? && node.implicit_call?) || (implicit_style? && !node.implicit_call?)
53
64
  end
@@ -6,6 +6,17 @@ module RuboCop
6
6
  # Prefer `select` or `reject` over `map { ... }.compact`.
7
7
  # This cop also handles `filter_map { ... }`, similar to `map { ... }.compact`.
8
8
  #
9
+ # @safety
10
+ # This cop is unsafe because `compact` also removes `nil` elements that
11
+ # were already present in the receiver, whereas `select`/`reject` keep
12
+ # them. The result therefore differs when the collection contains `nil`:
13
+ #
14
+ # [source,ruby]
15
+ # ----
16
+ # [nil, 1].map { |e| e if e }.compact # => [1]
17
+ # [nil, 1].select { |e| e } # => [nil, 1]
18
+ # ----
19
+ #
9
20
  # @example
10
21
  #
11
22
  # # bad
@@ -65,7 +65,7 @@ module RuboCop
65
65
 
66
66
  # @!method suitable_argument_node?(node)
67
67
  def_node_matcher :suitable_argument_node?, <<-PATTERN
68
- !{splat forwarded-restarg forwarded-args (hash (forwarded-kwrestarg)) (block-pass nil?)}
68
+ !{splat forwarded-restarg forwarded-args (hash (forwarded-kwrestarg)) block-pass}
69
69
  PATTERN
70
70
 
71
71
  # @!method each_block_with_push?(node)
@@ -50,9 +50,11 @@ module RuboCop
50
50
  private
51
51
 
52
52
  def register_offense(node)
53
- add_offense(offense_range(node)) do |corrector|
54
- corrector.remove(node.loc.begin)
55
- corrector.remove(node.loc.end)
53
+ range = offense_range(node)
54
+ return if processed_source.contains_comment?(range)
55
+
56
+ add_offense(range) do |corrector|
57
+ corrector.remove(range)
56
58
  end
57
59
  end
58
60
 
@@ -88,8 +90,12 @@ module RuboCop
88
90
  #
89
91
  def parenthesized_it_method_in_block?(node)
90
92
  return false unless node.method?(:it)
91
- return false unless (block_node = node.each_ancestor(:block).first)
92
- return false unless block_node.arguments.empty_and_without_delimiters?
93
+ return false unless (block_node = node.each_ancestor(:any_block).first)
94
+ # Inside a numbered/`it` block, a bare `it` is a parse error (it conflicts
95
+ # with the implicit parameter), so `it()` must keep its parentheses.
96
+ if block_node.block_type? && !block_node.arguments.empty_and_without_delimiters?
97
+ return false
98
+ end
93
99
 
94
100
  !node.receiver && node.arguments.empty? && !node.block_literal?
95
101
  end
@@ -102,6 +102,10 @@ module RuboCop
102
102
  MSG_PRESENT = 'Use def without parentheses.'
103
103
  MSG_MISSING = 'Use def with parentheses when there are parameters.'
104
104
 
105
+ def self.autocorrect_incompatible_with
106
+ [Style::ArgumentsForwarding]
107
+ end
108
+
105
109
  def on_def(node)
106
110
  args = node.arguments
107
111
 
@@ -166,7 +170,7 @@ module RuboCop
166
170
 
167
171
  def anonymous_arguments?(node)
168
172
  return true if node.arguments.any? do |arg|
169
- arg.type?(:forward_arg, :restarg, :kwrestarg)
173
+ arg.forward_arg_type? || (arg.type?(:restarg, :kwrestarg) && arg.name.nil?)
170
174
  end
171
175
  return false unless (last_argument = node.last_argument)
172
176
 
@@ -55,8 +55,11 @@ module RuboCop
55
55
  lhs, operator, rhs = comparison_condition(node.condition)
56
56
  return unless operator
57
57
 
58
+ # For `unless`, the branches run opposite to an `if`, so swap them to
59
+ # keep the `max`/`min` decision correct.
58
60
  if_branch = node.if_branch
59
61
  else_branch = node.else_branch
62
+ if_branch, else_branch = else_branch, if_branch if node.unless?
60
63
  preferred_method = preferred_method(operator, lhs, rhs, if_branch, else_branch)
61
64
  return unless preferred_method
62
65
 
@@ -65,16 +65,19 @@ module RuboCop
65
65
  private
66
66
 
67
67
  def implements_respond_to_missing?(node)
68
- return false unless (grand_parent = node.parent.parent)
68
+ scope = enclosing_scope(node)
69
+ search_root = scope || node.parent
70
+ return false unless search_root
69
71
 
70
- grand_parent.each_descendant(node.type) do |descendant|
71
- return true if descendant.method?(:respond_to_missing?)
72
-
73
- child = descendant.children.first
74
- return true if child.respond_to?(:method?) && child.method?(:respond_to_missing?)
72
+ search_root.each_descendant(node.type).any? do |descendant|
73
+ descendant.method?(:respond_to_missing?) && enclosing_scope(descendant).equal?(scope)
75
74
  end
75
+ end
76
76
 
77
- false
77
+ # The class/module/`class << self` body that lexically contains `node`,
78
+ # or `nil` when `node` is defined at the top level.
79
+ def enclosing_scope(node)
80
+ node.each_ancestor(:class, :module, :sclass).first
78
81
  end
79
82
  end
80
83
  end
@@ -47,7 +47,7 @@ module RuboCop
47
47
  # @!method include_statement(node)
48
48
  def_node_matcher :include_statement, <<~PATTERN
49
49
  (send nil? ${:include :extend :prepend}
50
- const)
50
+ const+)
51
51
  PATTERN
52
52
 
53
53
  # @!method in_top_level_scope?(node)
@@ -110,9 +110,11 @@ module RuboCop
110
110
  def_node_matcher :private_directive?, '(send nil? :private ...)'
111
111
 
112
112
  def on_module(node)
113
- return unless node.body&.begin_type?
113
+ return unless node.body
114
114
 
115
- each_wrong_style(node.body.children) do |child_node|
115
+ body_nodes = node.body.begin_type? ? node.body.children : [node.body]
116
+
117
+ each_wrong_style(body_nodes) do |child_node|
116
118
  add_offense(child_node) do |corrector|
117
119
  next if style == :forbidden
118
120
 
@@ -36,7 +36,7 @@ module RuboCop
36
36
  private
37
37
 
38
38
  def non_modifier_then?(node)
39
- node.then? && node.loc.begin.line != node.if_branch&.loc&.line
39
+ node.multiline? && node.then? && node.loc.begin.line != node.if_branch&.loc&.line
40
40
  end
41
41
  end
42
42
  end
@@ -65,10 +65,16 @@ module RuboCop
65
65
  if style == :keyword
66
66
  rhs.begin_type?
67
67
  else
68
- rhs.kwbegin_type?
68
+ # A `begin` block with `rescue`/`ensure` cannot be expressed with
69
+ # parentheses, so wrapping it in `(` and `)` is not possible.
70
+ rhs.kwbegin_type? && !contains_rescue_or_ensure?(rhs)
69
71
  end
70
72
  end
71
73
 
74
+ def contains_rescue_or_ensure?(node)
75
+ node.each_child_node(:rescue, :ensure).any?
76
+ end
77
+
72
78
  def keyword_autocorrect(node, corrector)
73
79
  node_buf = node.source_range.source_buffer
74
80
  corrector.replace(node.loc.begin, keyword_begin_str(node, node_buf))
@@ -51,10 +51,12 @@ module RuboCop
51
51
  end
52
52
 
53
53
  arguments_range = range_with_surrounding_space(arguments_range(node), side: :left)
54
- # If the method name isn't on the same line as def, move it directly after def
54
+ # If the method name isn't on the same line as `def`, pull the name and
55
+ # the opening parenthesis up next to `def` so the collapsed signature
56
+ # stays on a single line and remains valid Ruby.
55
57
  if arguments_range.first_line != opening_line(node)
56
- corrector.remove(node.loc.name)
57
- corrector.insert_after(node.loc.keyword, " #{node.loc.name.source}")
58
+ prefix_range = range_between(node.loc.keyword.end_pos, begin_of_arguments.begin_pos)
59
+ corrector.replace(prefix_range, " #{prefix_range.source.strip}")
58
60
  end
59
61
 
60
62
  corrector.remove(arguments_range)
@@ -85,7 +87,12 @@ module RuboCop
85
87
  end
86
88
 
87
89
  def definition_width(node)
88
- node.source_range.begin.join(node.arguments.source_range.end).length
90
+ # Measure the collapsed single-line width the autocorrect would
91
+ # produce, not the multi-line source length, so a signature that
92
+ # would fit on one line is not skipped.
93
+ signature = node.source_range.begin.join(node.arguments.source_range.end).source
94
+
95
+ signature.gsub(/\s+/, ' ').length
89
96
  end
90
97
  end
91
98
  end