rubocop 1.87.0 → 1.88.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (162) hide show
  1. checksums.yaml +4 -4
  2. data/config/default.yml +78 -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 +17 -2
  6. data/lib/rubocop/cop/bundler/gem_comment.rb +5 -3
  7. data/lib/rubocop/cop/correctors/each_to_for_corrector.rb +1 -1
  8. data/lib/rubocop/cop/correctors/lambda_literal_to_method_corrector.rb +7 -1
  9. data/lib/rubocop/cop/correctors/ordered_gem_corrector.rb +8 -1
  10. data/lib/rubocop/cop/gemspec/development_dependencies.rb +1 -1
  11. data/lib/rubocop/cop/gemspec/require_mfa.rb +4 -1
  12. data/lib/rubocop/cop/internal_affairs/redundant_let_rubocop_config_new.rb +5 -3
  13. data/lib/rubocop/cop/layout/block_alignment.rb +58 -4
  14. data/lib/rubocop/cop/layout/class_structure.rb +7 -3
  15. data/lib/rubocop/cop/layout/condition_position.rb +13 -3
  16. data/lib/rubocop/cop/layout/empty_comment.rb +8 -10
  17. data/lib/rubocop/cop/layout/empty_line_between_defs.rb +14 -1
  18. data/lib/rubocop/cop/layout/first_hash_element_indentation.rb +13 -14
  19. data/lib/rubocop/cop/layout/indentation_width.rb +28 -0
  20. data/lib/rubocop/cop/layout/space_around_operators.rb +6 -2
  21. data/lib/rubocop/cop/lint/ambiguous_assignment.rb +1 -11
  22. data/lib/rubocop/cop/lint/ambiguous_operator_precedence.rb +1 -10
  23. data/lib/rubocop/cop/lint/assignment_in_condition.rb +13 -1
  24. data/lib/rubocop/cop/lint/circular_argument_reference.rb +1 -3
  25. data/lib/rubocop/cop/lint/debugger.rb +0 -1
  26. data/lib/rubocop/cop/lint/deprecated_constants.rb +1 -7
  27. data/lib/rubocop/cop/lint/empty_block.rb +3 -3
  28. data/lib/rubocop/cop/lint/ensure_return.rb +19 -1
  29. data/lib/rubocop/cop/lint/erb_new_arguments.rb +3 -1
  30. data/lib/rubocop/cop/lint/float_comparison.rb +1 -0
  31. data/lib/rubocop/cop/lint/incompatible_io_select_with_fiber_scheduler.rb +5 -1
  32. data/lib/rubocop/cop/lint/interpolation_check.rb +18 -3
  33. data/lib/rubocop/cop/lint/lambda_without_literal_block.rb +1 -1
  34. data/lib/rubocop/cop/lint/literal_assignment_in_condition.rb +11 -1
  35. data/lib/rubocop/cop/lint/literal_in_interpolation.rb +8 -11
  36. data/lib/rubocop/cop/lint/missing_cop_enable_directive.rb +4 -4
  37. data/lib/rubocop/cop/lint/no_return_in_begin_end_blocks.rb +16 -0
  38. data/lib/rubocop/cop/lint/non_local_exit_from_iterator.rb +1 -1
  39. data/lib/rubocop/cop/lint/number_conversion.rb +13 -4
  40. data/lib/rubocop/cop/lint/numeric_operation_with_constant_result.rb +3 -0
  41. data/lib/rubocop/cop/lint/ordered_magic_comments.rb +7 -7
  42. data/lib/rubocop/cop/lint/raise_exception.rb +1 -1
  43. data/lib/rubocop/cop/lint/rand_one.rb +1 -1
  44. data/lib/rubocop/cop/lint/redundant_cop_disable_directive.rb +4 -1
  45. data/lib/rubocop/cop/lint/redundant_cop_enable_directive.rb +4 -1
  46. data/lib/rubocop/cop/lint/redundant_dir_glob_sort.rb +15 -4
  47. data/lib/rubocop/cop/lint/redundant_safe_navigation.rb +14 -7
  48. data/lib/rubocop/cop/lint/redundant_splat_expansion.rb +4 -0
  49. data/lib/rubocop/cop/lint/redundant_type_conversion.rb +7 -0
  50. data/lib/rubocop/cop/lint/redundant_with_index.rb +1 -1
  51. data/lib/rubocop/cop/lint/redundant_with_object.rb +5 -0
  52. data/lib/rubocop/cop/lint/refinement_import_methods.rb +8 -1
  53. data/lib/rubocop/cop/lint/regexp_as_condition.rb +9 -1
  54. data/lib/rubocop/cop/lint/require_parentheses.rb +13 -4
  55. data/lib/rubocop/cop/lint/require_range_parentheses.rb +2 -1
  56. data/lib/rubocop/cop/lint/require_relative_self_path.rb +5 -5
  57. data/lib/rubocop/cop/lint/rescue_type.rb +1 -1
  58. data/lib/rubocop/cop/lint/safe_navigation_chain.rb +1 -0
  59. data/lib/rubocop/cop/lint/safe_navigation_with_empty.rb +1 -1
  60. data/lib/rubocop/cop/lint/script_permission.rb +5 -1
  61. data/lib/rubocop/cop/lint/self_assignment.rb +24 -1
  62. data/lib/rubocop/cop/lint/send_with_mixin_argument.rb +1 -1
  63. data/lib/rubocop/cop/lint/shadowing_outer_local_variable.rb +14 -0
  64. data/lib/rubocop/cop/lint/shared_mutable_default.rb +3 -1
  65. data/lib/rubocop/cop/lint/suppressed_exception_in_number_conversion.rb +12 -0
  66. data/lib/rubocop/cop/lint/symbol_conversion.rb +21 -4
  67. data/lib/rubocop/cop/lint/to_enum_arguments.rb +35 -2
  68. data/lib/rubocop/cop/lint/top_level_return_with_argument.rb +1 -1
  69. data/lib/rubocop/cop/lint/trailing_comma_in_attribute_declaration.rb +4 -1
  70. data/lib/rubocop/cop/lint/unescaped_bracket_in_regexp.rb +35 -9
  71. data/lib/rubocop/cop/lint/useless_assignment.rb +10 -5
  72. data/lib/rubocop/cop/lint/useless_ruby2_keywords.rb +7 -3
  73. data/lib/rubocop/cop/lint/useless_setter_call.rb +4 -1
  74. data/lib/rubocop/cop/lint/useless_times.rb +22 -1
  75. data/lib/rubocop/cop/metrics/collection_literal_length.rb +1 -1
  76. data/lib/rubocop/cop/metrics/method_length.rb +1 -1
  77. data/lib/rubocop/cop/metrics/perceived_complexity.rb +38 -7
  78. data/lib/rubocop/cop/mixin/hash_subset.rb +8 -0
  79. data/lib/rubocop/cop/mixin/hash_transform_method.rb +4 -0
  80. data/lib/rubocop/cop/naming/file_name.rb +4 -3
  81. data/lib/rubocop/cop/naming/inclusive_language.rb +8 -2
  82. data/lib/rubocop/cop/naming/memoized_instance_variable_name.rb +9 -0
  83. data/lib/rubocop/cop/naming/rescued_exceptions_variable_name.rb +9 -3
  84. data/lib/rubocop/cop/security/io_methods.rb +1 -1
  85. data/lib/rubocop/cop/security/marshal_load.rb +1 -1
  86. data/lib/rubocop/cop/style/accessor_grouping.rb +11 -1
  87. data/lib/rubocop/cop/style/alias.rb +1 -1
  88. data/lib/rubocop/cop/style/and_or.rb +1 -1
  89. data/lib/rubocop/cop/style/array_first_last.rb +12 -1
  90. data/lib/rubocop/cop/style/array_intersect.rb +4 -0
  91. data/lib/rubocop/cop/style/array_intersect_with_single_element.rb +3 -0
  92. data/lib/rubocop/cop/style/block_delimiters.rb +16 -2
  93. data/lib/rubocop/cop/style/case_equality.rb +14 -2
  94. data/lib/rubocop/cop/style/class_equality_comparison.rb +21 -13
  95. data/lib/rubocop/cop/style/class_methods_definitions.rb +11 -5
  96. data/lib/rubocop/cop/style/colon_method_call.rb +13 -6
  97. data/lib/rubocop/cop/style/combinable_loops.rb +5 -0
  98. data/lib/rubocop/cop/style/comparable_clamp.rb +12 -1
  99. data/lib/rubocop/cop/style/concat_array_literals.rb +5 -1
  100. data/lib/rubocop/cop/style/conditional_assignment.rb +6 -1
  101. data/lib/rubocop/cop/style/constant_visibility.rb +4 -1
  102. data/lib/rubocop/cop/style/data_inheritance.rb +4 -0
  103. data/lib/rubocop/cop/style/date_time.rb +2 -2
  104. data/lib/rubocop/cop/style/dig_chain.rb +5 -0
  105. data/lib/rubocop/cop/style/dir_empty.rb +4 -0
  106. data/lib/rubocop/cop/style/empty_case_condition.rb +12 -2
  107. data/lib/rubocop/cop/style/empty_class_definition.rb +8 -1
  108. data/lib/rubocop/cop/style/empty_heredoc.rb +4 -0
  109. data/lib/rubocop/cop/style/empty_literal.rb +7 -2
  110. data/lib/rubocop/cop/style/empty_string_inside_interpolation.rb +30 -20
  111. data/lib/rubocop/cop/style/env_home.rb +4 -0
  112. data/lib/rubocop/cop/style/even_odd.rb +11 -1
  113. data/lib/rubocop/cop/style/exact_regexp_match.rb +8 -1
  114. data/lib/rubocop/cop/style/fetch_env_var.rb +1 -1
  115. data/lib/rubocop/cop/style/file_null.rb +4 -2
  116. data/lib/rubocop/cop/style/file_write.rb +17 -14
  117. data/lib/rubocop/cop/style/format_string.rb +13 -1
  118. data/lib/rubocop/cop/style/hash_slice.rb +16 -0
  119. data/lib/rubocop/cop/style/hash_syntax.rb +2 -0
  120. data/lib/rubocop/cop/style/if_unless_modifier.rb +1 -1
  121. data/lib/rubocop/cop/style/if_with_semicolon.rb +9 -1
  122. data/lib/rubocop/cop/style/inline_comment.rb +1 -1
  123. data/lib/rubocop/cop/style/keyword_arguments_merging.rb +4 -0
  124. data/lib/rubocop/cop/style/keyword_parameters_order.rb +7 -3
  125. data/lib/rubocop/cop/style/lambda.rb +7 -1
  126. data/lib/rubocop/cop/style/map_compact_with_conditional_block.rb +11 -0
  127. data/lib/rubocop/cop/style/map_into_array.rb +1 -1
  128. data/lib/rubocop/cop/style/method_call_without_args_parentheses.rb +6 -2
  129. data/lib/rubocop/cop/style/method_def_parentheses.rb +1 -1
  130. data/lib/rubocop/cop/style/min_max_comparison.rb +3 -0
  131. data/lib/rubocop/cop/style/multiline_if_then.rb +1 -1
  132. data/lib/rubocop/cop/style/multiline_memoization.rb +7 -1
  133. data/lib/rubocop/cop/style/multiline_method_signature.rb +11 -4
  134. data/lib/rubocop/cop/style/mutable_constant.rb +105 -11
  135. data/lib/rubocop/cop/style/nil_lambda.rb +8 -0
  136. data/lib/rubocop/cop/style/numeric_predicate.rb +1 -1
  137. data/lib/rubocop/cop/style/open_struct_use.rb +1 -1
  138. data/lib/rubocop/cop/style/option_hash.rb +1 -1
  139. data/lib/rubocop/cop/style/optional_arguments.rb +1 -0
  140. data/lib/rubocop/cop/style/parallel_assignment.rb +19 -3
  141. data/lib/rubocop/cop/style/percent_literal_delimiters.rb +2 -0
  142. data/lib/rubocop/cop/style/perl_backrefs.rb +5 -3
  143. data/lib/rubocop/cop/style/redundant_exception.rb +6 -0
  144. data/lib/rubocop/cop/style/redundant_filter_chain.rb +1 -1
  145. data/lib/rubocop/cop/style/redundant_format.rb +29 -0
  146. data/lib/rubocop/cop/style/redundant_line_continuation.rb +11 -3
  147. data/lib/rubocop/cop/style/redundant_regexp_escape.rb +8 -4
  148. data/lib/rubocop/cop/style/redundant_self.rb +9 -0
  149. data/lib/rubocop/cop/style/redundant_struct_keyword_init.rb +23 -4
  150. data/lib/rubocop/cop/style/semicolon.rb +20 -5
  151. data/lib/rubocop/cop/style/single_line_do_end_block.rb +17 -4
  152. data/lib/rubocop/cop/style/string_hash_keys.rb +1 -0
  153. data/lib/rubocop/cop/style/ternary_parentheses.rb +11 -0
  154. data/lib/rubocop/cop/style/trailing_underscore_variable.rb +7 -8
  155. data/lib/rubocop/cop/style/while_until_do.rb +7 -0
  156. data/lib/rubocop/cop/style/word_array.rb +1 -0
  157. data/lib/rubocop/cop/style/zero_length_predicate.rb +6 -3
  158. data/lib/rubocop/formatter/disabled_config_formatter.rb +14 -7
  159. data/lib/rubocop/runner.rb +5 -3
  160. data/lib/rubocop/server/core.rb +6 -0
  161. data/lib/rubocop/version.rb +1 -1
  162. metadata +3 -3
@@ -61,11 +61,15 @@ module RuboCop
61
61
  private
62
62
 
63
63
  def scheduler_compatible?(io1, io2)
64
- return false unless io1&.array_type? && io1.values.size == 1
64
+ return false unless single_io_array?(io1)
65
65
 
66
66
  io2&.array_type? ? io2.values.empty? : (io2.nil? || io2.nil_type?)
67
67
  end
68
68
 
69
+ def single_io_array?(node)
70
+ node&.array_type? && node.values.size == 1 && !node.values.first.splat_type?
71
+ end
72
+
69
73
  def preferred_method(read, write, timeout)
70
74
  timeout_argument = timeout.nil? ? '' : "(#{timeout.source})"
71
75
 
@@ -24,8 +24,25 @@ module RuboCop
24
24
  MSG = 'Interpolation in single quoted string detected. ' \
25
25
  'Use double quoted strings if you need interpolation.'
26
26
 
27
- # rubocop:disable Metrics/CyclomaticComplexity
28
27
  def on_str(node)
28
+ check(node)
29
+ end
30
+
31
+ # A multiline single-quoted string is parsed as a `dstr` of `str` segments, so it
32
+ # is not covered by `on_str`. Inspect single-quoted `dstr`s here; double-quoted
33
+ # interpolation is also a `dstr`, hence the delimiter check.
34
+ def on_dstr(node)
35
+ # A heredoc is also a `dstr`, but its `loc` is a `Parser::Source::Map::Heredoc`
36
+ # with no `begin`, so bail before touching it.
37
+ return if heredoc?(node)
38
+
39
+ check(node) if node.loc.begin&.source == "'"
40
+ end
41
+
42
+ private
43
+
44
+ # rubocop:disable Metrics/CyclomaticComplexity
45
+ def check(node)
29
46
  return if node.parent&.regexp_type?
30
47
  return unless /(?<!\\)#\{.*\}/.match?(node.source)
31
48
  return if heredoc?(node)
@@ -36,8 +53,6 @@ module RuboCop
36
53
  end
37
54
  # rubocop:enable Metrics/CyclomaticComplexity
38
55
 
39
- private
40
-
41
56
  def autocorrect(corrector, node)
42
57
  starting_token, ending_token = if node.source.include?('"')
43
58
  ['%{', '}']
@@ -42,7 +42,7 @@ module RuboCop
42
42
  end
43
43
 
44
44
  add_offense(node) do |corrector|
45
- corrector.replace(node, node.first_argument.source.delete('&'))
45
+ corrector.replace(node, node.first_argument.source.delete_prefix('&'))
46
46
  end
47
47
  end
48
48
  end
@@ -56,7 +56,17 @@ module RuboCop
56
56
  def traverse_node(node, &block)
57
57
  yield node if node.equals_asgn?
58
58
 
59
- node.each_child_node { |child| traverse_node(child, &block) }
59
+ node.each_child_node do |child|
60
+ next if scope_body?(node, child)
61
+
62
+ traverse_node(child, &block)
63
+ end
64
+ end
65
+
66
+ # An assignment inside a block or method body within the condition belongs to
67
+ # that inner scope rather than the condition itself, so it is not inspected.
68
+ def scope_body?(node, child)
69
+ node.type?(:any_block, :any_def) && child == node.body
60
70
  end
61
71
 
62
72
  def all_literals?(node)
@@ -119,11 +119,13 @@ module RuboCop
119
119
  end
120
120
 
121
121
  def autocorrected_value_for_string(node)
122
- if node.source.start_with?("'", '%q')
123
- node.children.last.inspect[1..-2]
124
- else
125
- node.children.last
126
- end
122
+ return node.source.delete_prefix('"').delete_suffix('"') unless node.value.valid_encoding?
123
+
124
+ escape_string_content(node.children.last)
125
+ end
126
+
127
+ def escape_string_content(string)
128
+ string.gsub(/[\\"]|#(?=[@{$])/, '\\\\\&')
127
129
  end
128
130
 
129
131
  def autocorrected_value_for_symbol(node)
@@ -134,12 +136,7 @@ module RuboCop
134
136
  end
135
137
 
136
138
  def autocorrected_value_in_hash_for_symbol(node)
137
- # TODO: We need to detect symbol unacceptable names more correctly
138
- if / |"|'/.match?(node.value.to_s)
139
- ":\\\"#{node.value.to_s.gsub('"') { '\\\\\"' }}\\\""
140
- else
141
- ":#{node.value}"
142
- end
139
+ escape_string_content(node.value.inspect)
143
140
  end
144
141
 
145
142
  def autocorrected_value_for_array(node)
@@ -9,7 +9,7 @@ module RuboCop
9
9
  # cop disables on wide ranges of code, that later contributors to
10
10
  # a file wouldn't be aware of.
11
11
  #
12
- # You can set `MaximumRangeSize` to define the maximum number of
12
+ # You can set `MaxRangeSize` to define the maximum number of
13
13
  # consecutive lines a cop can be disabled for.
14
14
  #
15
15
  # - `.inf` any size (default)
@@ -23,7 +23,7 @@ module RuboCop
23
23
  # # rubocop:enable SomeCop
24
24
  # ----
25
25
  #
26
- # @example MaximumRangeSize: .inf (default)
26
+ # @example MaxRangeSize: .inf (default)
27
27
  #
28
28
  # # good
29
29
  # # rubocop:disable Layout/SpaceAroundOperators
@@ -37,7 +37,7 @@ module RuboCop
37
37
  # x= 0
38
38
  # # EOF
39
39
  #
40
- # @example MaximumRangeSize: 2
40
+ # @example MaxRangeSize: 2
41
41
  #
42
42
  # # good
43
43
  # # rubocop:disable Layout/SpaceAroundOperators
@@ -94,7 +94,7 @@ module RuboCop
94
94
  end
95
95
 
96
96
  def max_range
97
- @max_range ||= cop_config['MaximumRangeSize']
97
+ @max_range ||= cop_config['MaxRangeSize']
98
98
  end
99
99
 
100
100
  def message(cop, comment, type = 'cop')
@@ -41,6 +41,8 @@ module RuboCop
41
41
  def on_lvasgn(node)
42
42
  node.each_node(:kwbegin) do |kwbegin_node|
43
43
  kwbegin_node.each_node(:return) do |return_node|
44
+ next if return_from_inner_scope?(return_node, kwbegin_node)
45
+
44
46
  add_offense(return_node)
45
47
  end
46
48
  end
@@ -51,6 +53,20 @@ module RuboCop
51
53
  alias on_casgn on_lvasgn
52
54
  alias on_or_asgn on_lvasgn
53
55
  alias on_op_asgn on_lvasgn
56
+
57
+ private
58
+
59
+ # A `return` inside a nested method definition or lambda within the
60
+ # `begin..end` returns from that inner scope rather than the assignment
61
+ # context, so it is not an offense. A `return` inside a plain block (or
62
+ # `proc`) does propagate out, so it remains an offense.
63
+ def return_from_inner_scope?(return_node, kwbegin_node)
64
+ return_node.each_ancestor do |ancestor|
65
+ break if ancestor == kwbegin_node
66
+ return true if ancestor.any_def_type? || (ancestor.any_block_type? && ancestor.lambda?)
67
+ end
68
+ false
69
+ end
54
70
  end
55
71
  end
56
72
  end
@@ -74,7 +74,7 @@ module RuboCop
74
74
  end
75
75
 
76
76
  # @!method chained_send?(node)
77
- def_node_matcher :chained_send?, '(send !nil? ...)'
77
+ def_node_matcher :chained_send?, '(call !nil? ...)'
78
78
 
79
79
  # @!method define_method?(node)
80
80
  def_node_matcher :define_method?, <<~PATTERN
@@ -66,7 +66,7 @@ module RuboCop
66
66
  # # good
67
67
  # 10.minutes.to_i
68
68
  #
69
- # @example IgnoredClasses: [Time, DateTime] (default)
69
+ # @example AllowedClasses: [Time, DateTime] (default)
70
70
  #
71
71
  # # good
72
72
  # Time.now.to_datetime.to_i
@@ -117,11 +117,11 @@ module RuboCop
117
117
 
118
118
  message = format(
119
119
  MSG,
120
- current: "#{receiver.source}.#{to_method}",
120
+ current: current_method(node, receiver, to_method),
121
121
  corrected_method: correct_method(node, receiver)
122
122
  )
123
123
  add_offense(node, message: message) do |corrector|
124
- next if part_of_ignored_node?(node)
124
+ next if safe_navigation?(node) || part_of_ignored_node?(node)
125
125
 
126
126
  corrector.replace(node, correct_method(node, node.receiver))
127
127
 
@@ -156,11 +156,20 @@ module RuboCop
156
156
  "{ |i| #{body} }"
157
157
  end
158
158
 
159
+ def current_method(node, receiver, to_method)
160
+ operator = node.csend_type? ? '&.' : '.'
161
+ "#{receiver.source}#{operator}#{to_method}"
162
+ end
163
+
159
164
  def remove_parentheses(corrector, node)
160
165
  corrector.replace(node.loc.begin, ' ')
161
166
  corrector.remove(node.loc.end)
162
167
  end
163
168
 
169
+ def safe_navigation?(node)
170
+ node.csend_type? || node.each_descendant(:csend).any?
171
+ end
172
+
164
173
  def allow_receiver?(receiver)
165
174
  if receiver.numeric_type? || (receiver.call_type? &&
166
175
  (conversion_method?(receiver.method_name) ||
@@ -188,7 +197,7 @@ module RuboCop
188
197
  end
189
198
 
190
199
  def ignored_classes
191
- cop_config.fetch('IgnoredClasses', [])
200
+ cop_config.fetch('AllowedClasses', [])
192
201
  end
193
202
 
194
203
  def ignored_class?(name)
@@ -58,6 +58,9 @@ module RuboCop
58
58
  '(op-asgn (lvasgn $_lhs) $_operation ({int lvar} $_rhs))'
59
59
 
60
60
  def on_send(node)
61
+ # Safe navigation short-circuits to `nil` when the receiver is `nil`, so the
62
+ # result is not constant and replacing it with `0`/`1` would change behavior.
63
+ return if node.csend_type?
61
64
  return unless (lhs, operation, rhs = operation_with_constant_result?(node))
62
65
  return unless (result = constant_result?(lhs, operation, rhs))
63
66
 
@@ -38,23 +38,23 @@ module RuboCop
38
38
  def on_new_investigation
39
39
  return if processed_source.buffer.source.empty?
40
40
 
41
- encoding_line, frozen_string_literal_line = magic_comment_lines
41
+ encoding_line, other_magic_comment_line = magic_comment_lines
42
42
 
43
- return unless encoding_line && frozen_string_literal_line
44
- return if encoding_line < frozen_string_literal_line
43
+ return unless encoding_line && other_magic_comment_line
44
+ return if encoding_line < other_magic_comment_line
45
45
 
46
46
  range = processed_source.buffer.line_range(encoding_line + 1)
47
47
 
48
48
  add_offense(range) do |corrector|
49
- autocorrect(corrector, encoding_line, frozen_string_literal_line)
49
+ autocorrect(corrector, encoding_line, other_magic_comment_line)
50
50
  end
51
51
  end
52
52
 
53
53
  private
54
54
 
55
- def autocorrect(corrector, encoding_line, frozen_string_literal_line)
55
+ def autocorrect(corrector, encoding_line, other_magic_comment_line)
56
56
  range1 = processed_source.buffer.line_range(encoding_line + 1)
57
- range2 = processed_source.buffer.line_range(frozen_string_literal_line + 1)
57
+ range2 = processed_source.buffer.line_range(other_magic_comment_line + 1)
58
58
 
59
59
  corrector.replace(range1, range2.source)
60
60
  corrector.replace(range2, range1.source)
@@ -66,7 +66,7 @@ module RuboCop
66
66
  leading_magic_comments.each.with_index do |comment, index|
67
67
  if comment.encoding_specified?
68
68
  lines[0] = index
69
- elsif comment.frozen_string_literal_specified?
69
+ elsif comment.valid?
70
70
  lines[1] = index
71
71
  end
72
72
 
@@ -95,7 +95,7 @@ module RuboCop
95
95
  if parent.module_type?
96
96
  namespace = parent.identifier.source
97
97
 
98
- return allow_implicit_namespaces.include?(namespace)
98
+ return true if allow_implicit_namespaces.include?(namespace)
99
99
  end
100
100
 
101
101
  implicit_namespace?(parent)
@@ -4,7 +4,7 @@ module RuboCop
4
4
  module Cop
5
5
  module Lint
6
6
  # Checks for `rand(1)` calls.
7
- # Such calls always return `0`.
7
+ # Such calls always return `0`, and so do `rand(-1)`, `rand(1.0)`, and `rand(-1.0)`.
8
8
  #
9
9
  # @example
10
10
  #
@@ -283,7 +283,10 @@ module RuboCop
283
283
  end
284
284
 
285
285
  def matching_range(haystack, needle)
286
- offset = haystack.source.index(needle)
286
+ # Match the cop name as a whole token so a shorter name is not found inside a
287
+ # longer one that shares its prefix (e.g. `Lint/AmbiguousOperator` in
288
+ # `Lint/AmbiguousOperatorPrecedence`).
289
+ offset = haystack.source.index(/#{Regexp.escape(needle)}(?!\w)/)
287
290
  return unless offset
288
291
 
289
292
  offset += haystack.begin_pos
@@ -74,7 +74,10 @@ module RuboCop
74
74
  end
75
75
 
76
76
  def cop_name_indention(comment, name)
77
- comment.text.index(name)
77
+ # Match the cop name as a whole token so a shorter name is not found inside a
78
+ # longer one that shares its prefix (e.g. `Layout/EmptyLines` in
79
+ # `Layout/EmptyLinesAfterModuleInclusion`).
80
+ comment.text.index(/#{Regexp.escape(name)}(?!\w)/)
78
81
  end
79
82
 
80
83
  def range_with_comma(comment, name)
@@ -38,10 +38,10 @@ module RuboCop
38
38
  GLOB_METHODS = %i[glob []].freeze
39
39
 
40
40
  def on_send(node)
41
- return unless (receiver = node.receiver)
42
- return unless receiver.receiver&.const_type? && receiver.receiver.short_name == :Dir
43
- return unless GLOB_METHODS.include?(receiver.method_name)
44
- return if multiple_argument?(receiver)
41
+ return unless dir_glob?(node.receiver)
42
+ # `sort` with a comparator block or block-pass changes the order, so it is
43
+ # not redundant with the default sorting performed by `Dir.glob`/`Dir[]`.
44
+ return if sort_with_comparator?(node) || multiple_argument?(node.receiver)
45
45
 
46
46
  selector = node.loc.selector
47
47
 
@@ -53,9 +53,20 @@ module RuboCop
53
53
 
54
54
  private
55
55
 
56
+ def dir_glob?(receiver)
57
+ return false unless receiver&.receiver&.const_type?
58
+ return false unless receiver.receiver.short_name == :Dir
59
+
60
+ GLOB_METHODS.include?(receiver.method_name)
61
+ end
62
+
56
63
  def multiple_argument?(glob_method)
57
64
  glob_method.arguments.count >= 2 || glob_method.first_argument&.splat_type?
58
65
  end
66
+
67
+ def sort_with_comparator?(node)
68
+ node.parent&.any_block_type? || node.last_argument&.block_pass_type?
69
+ end
59
70
  end
60
71
  end
61
72
  end
@@ -183,7 +183,7 @@ module RuboCop
183
183
  def_node_matcher :conversion_with_default?, <<~PATTERN
184
184
  {
185
185
  (or $(csend _ :to_h) (hash))
186
- (or (block $(csend _ :to_h) ...) (hash))
186
+ (or (any_block $(csend _ :to_h) ...) (hash))
187
187
  (or $(csend _ :to_a) (array))
188
188
  (or $(csend _ :to_i) (int 0))
189
189
  (or $(csend _ :to_f) (float 0.0))
@@ -191,7 +191,6 @@ module RuboCop
191
191
  }
192
192
  PATTERN
193
193
 
194
- # rubocop:disable Metrics/AbcSize
195
194
  def on_csend(node)
196
195
  range = node.loc.dot
197
196
 
@@ -204,14 +203,10 @@ module RuboCop
204
203
  end
205
204
  end
206
205
 
207
- unless assume_receiver_instance_exists?(node.receiver)
208
- return if !guaranteed_instance?(node.receiver) && !check?(node)
209
- return if respond_to_nil_method?(node)
210
- end
206
+ return if guarded_by_nil_receiver?(node)
211
207
 
212
208
  add_offense(range) { |corrector| corrector.replace(range, '.') }
213
209
  end
214
- # rubocop:enable Metrics/AbcSize
215
210
 
216
211
  # rubocop:disable Metrics/AbcSize
217
212
  def on_or(node)
@@ -230,6 +225,18 @@ module RuboCop
230
225
 
231
226
  private
232
227
 
228
+ # Returns true when the `&.` is meaningful because the receiver may actually be nil.
229
+ def guarded_by_nil_receiver?(node)
230
+ return false if assume_receiver_instance_exists?(node.receiver)
231
+
232
+ guaranteed_instance = guaranteed_instance?(node.receiver)
233
+ return true if !guaranteed_instance && !check?(node)
234
+
235
+ # `nil.respond_to?(<nil method>)` is `true`, so `&.` is meaningful when the receiver
236
+ # may be nil. A guaranteed instance can never be nil, so `&.` is still redundant there.
237
+ respond_to_nil_method?(node) && !guaranteed_instance
238
+ end
239
+
233
240
  def assume_receiver_instance_exists?(receiver)
234
241
  return true if receiver.const_type? && !receiver.short_name.match?(SNAKE_CASE)
235
242
 
@@ -122,6 +122,10 @@ module RuboCop
122
122
 
123
123
  grandparent = node.parent.parent
124
124
  return if grandparent && !ASSIGNMENT_TYPES.include?(grandparent.type)
125
+ # An empty array/percent literal (`*[]`, `*%w()`, ...) expands to nothing, so
126
+ # removing the splat would produce invalid or semantically different code.
127
+ elsif expanded_item.array_type? && expanded_item.children.empty?
128
+ return
125
129
  end
126
130
 
127
131
  yield
@@ -18,6 +18,7 @@ module RuboCop
18
18
  # * `to_sym` when called on a symbol literal or interpolated symbol.
19
19
  # * `to_i` when called on an integer literal or with `Integer()`.
20
20
  # * `to_f` when called on a float literal or with `Float()`.
21
+ # * `to_d` when called with `BigDecimal()`.
21
22
  # * `to_r` when called on a rational literal or with `Rational()`.
22
23
  # * `to_c` when called on a complex literal or with `Complex()`.
23
24
  # * `to_a` when called on an array literal, or with `Array.new`, `Array()` or `Array[]`.
@@ -63,6 +64,12 @@ module RuboCop
63
64
  # # in this case, `Integer()` could return `nil`
64
65
  # Integer(var, exception: false).to_i
65
66
  #
67
+ # # bad
68
+ # BigDecimal(var).to_d
69
+ #
70
+ # # good
71
+ # BigDecimal(var)
72
+ #
66
73
  # # bad - chaining the same conversion
67
74
  # foo.to_s.to_s
68
75
  #
@@ -62,7 +62,7 @@ module RuboCop
62
62
  {
63
63
  (block
64
64
  $(call _ {:each_with_index :with_index} ...)
65
- (args (arg _)) ...)
65
+ {(args (arg _)) (args)} ...)
66
66
  (numblock
67
67
  $(call _ {:each_with_index :with_index} ...) 1 ...)
68
68
  (itblock
@@ -5,6 +5,11 @@ module RuboCop
5
5
  module Lint
6
6
  # Checks for redundant `with_object`.
7
7
  #
8
+ # @safety
9
+ # This cop's autocorrection is unsafe because the return value changes:
10
+ # `each_with_object` returns the memo object, while the corrected `each` returns
11
+ # the receiver. This matters when the result of the expression is used.
12
+ #
8
13
  # @example
9
14
  # # bad
10
15
  # ary.each_with_object([]) do |v|
@@ -32,9 +32,12 @@ module RuboCop
32
32
  # end
33
33
  #
34
34
  class RefinementImportMethods < Base
35
+ extend AutoCorrector
35
36
  extend TargetRubyVersion
36
37
 
37
38
  MSG = 'Use `import_methods` instead of `%<current>s` because it is deprecated in Ruby 3.1.'
39
+ MSG_REMOVED = 'Use `import_methods` instead of `%<current>s` ' \
40
+ 'because it was removed in Ruby 3.2.'
38
41
  RESTRICT_ON_SEND = %i[include prepend].freeze
39
42
 
40
43
  minimum_target_ruby_version 3.1
@@ -44,7 +47,11 @@ module RuboCop
44
47
  return unless (parent = node.parent)
45
48
  return unless parent.block_type? && parent.method?(:refine)
46
49
 
47
- add_offense(node.loc.selector, message: format(MSG, current: node.method_name))
50
+ template = target_ruby_version >= 3.2 ? MSG_REMOVED : MSG
51
+ message = format(template, current: node.method_name)
52
+ add_offense(node.loc.selector, message: message) do |corrector|
53
+ corrector.replace(node.loc.selector, 'import_methods')
54
+ end
48
55
  end
49
56
  end
50
57
  end
@@ -26,7 +26,15 @@ module RuboCop
26
26
  return if node.ancestors.none?(&:conditional?)
27
27
  return if part_of_ignored_node?(node)
28
28
 
29
- add_offense(node) { |corrector| corrector.replace(node, "#{node.source} =~ $_") }
29
+ add_offense(node) do |corrector|
30
+ # `!` binds tighter than `=~`, so `!/foo/ =~ $_` would parse as
31
+ # `(!/foo/) =~ $_`. Wrap the match in parentheses to preserve the meaning.
32
+ if node.parent&.send_type? && node.parent.method?(:!)
33
+ corrector.replace(node.parent, "!(#{node.source} =~ $_)")
34
+ else
35
+ corrector.replace(node, "#{node.source} =~ $_")
36
+ end
37
+ end
30
38
 
31
39
  ignore_node(node)
32
40
  end
@@ -3,10 +3,13 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Lint
6
- # Checks for expressions where there is a call to a predicate
7
- # method with at least one argument, where no parentheses are used around
8
- # the parameter list, and a boolean operator, && or ||, is used in the
9
- # last argument.
6
+ # Checks for method calls with at least one argument where no parentheses
7
+ # are used around the parameter list, and the call could be misread as an
8
+ # operand of a boolean operator (`&&` or `||`). Two forms are flagged:
9
+ #
10
+ # * a predicate method whose last argument is a `&&`/`||` expression, and
11
+ # * any method whose first argument is a ternary expression with a
12
+ # `&&`/`||` condition.
10
13
  #
11
14
  # The idea behind warning for these constructs is that the user might
12
15
  # be under the impression that the return value from the method call is
@@ -23,6 +26,12 @@ module RuboCop
23
26
  # if day.is?(:tuesday) && month == :jan
24
27
  # # ...
25
28
  # end
29
+ #
30
+ # # bad
31
+ # foo a && b ? c : d
32
+ #
33
+ # # good
34
+ # foo(a && b ? c : d)
26
35
  class RequireParentheses < Base
27
36
  include RangeHelp
28
37
 
@@ -38,7 +38,8 @@ module RuboCop
38
38
  # 42)
39
39
  #
40
40
  class RequireRangeParentheses < Base
41
- MSG = 'Wrap the endless range literal `%<range>s` to avoid precedence ambiguity.'
41
+ MSG = 'Wrap the range literal `%<range>s` in parentheses ' \
42
+ 'to avoid confusion with an endless range.'
42
43
 
43
44
  def on_irange(node)
44
45
  return if node.parent&.begin_type?
@@ -40,11 +40,11 @@ module RuboCop
40
40
  def same_file?(file_path, required_feature)
41
41
  return false unless File.extname(file_path) == '.rb'
42
42
 
43
- file_path == required_feature || remove_ext(file_path) == required_feature
44
- end
45
-
46
- def remove_ext(file_path)
47
- File.basename(file_path, File.extname(file_path))
43
+ # `require_relative` is resolved relative to the current file's directory, so a
44
+ # bare `foo`/`foo.rb` (no path separator) requires the current file itself. Compare
45
+ # against the basename so this works whether `file_path` is relative or absolute.
46
+ basename = File.basename(file_path, '.rb')
47
+ required_feature == basename || required_feature == "#{basename}.rb"
48
48
  end
49
49
  end
50
50
  end
@@ -39,7 +39,7 @@ module RuboCop
39
39
 
40
40
  MSG = 'Rescuing from `%<invalid_exceptions>s` will raise a ' \
41
41
  '`TypeError` instead of catching the actual exception.'
42
- INVALID_TYPES = %i[array dstr float hash nil int str sym].freeze
42
+ INVALID_TYPES = %i[array complex dstr false float hash nil int rational str sym true].freeze
43
43
 
44
44
  def on_resbody(node)
45
45
  invalid_exceptions = invalid_exceptions(node.exceptions)
@@ -34,6 +34,7 @@ module RuboCop
34
34
  {
35
35
  (send $(csend ...) $_ ...)
36
36
  (send $(any_block (csend ...) ...) $_ ...)
37
+ (send $(begin (csend ...)) $_ ...)
37
38
  }
38
39
  PATTERN
39
40
 
@@ -26,7 +26,7 @@ module RuboCop
26
26
 
27
27
  # @!method safe_navigation_empty_in_conditional?(node)
28
28
  def_node_matcher :safe_navigation_empty_in_conditional?, <<~PATTERN
29
- (if (csend (send ...) :empty?) ...)
29
+ (if (csend !csend :empty?) ...)
30
30
  PATTERN
31
31
 
32
32
  def on_if(node)
@@ -46,7 +46,7 @@ module RuboCop
46
46
  message = format_message_from(processed_source)
47
47
 
48
48
  add_offense(comment, message: message) do
49
- autocorrect if autocorrect_requested?
49
+ autocorrect if autocorrect?
50
50
  end
51
51
  end
52
52
 
@@ -57,6 +57,10 @@ module RuboCop
57
57
  end
58
58
 
59
59
  def executable?(processed_source)
60
+ # Virtual sources (LSP buffers, programmatic `ProcessedSource`) have no file on
61
+ # disk to stat or `chmod`, so treat them as executable to skip the offense.
62
+ return true unless File.exist?(processed_source.file_path)
63
+
60
64
  # Returns true if stat is executable or if the operating system
61
65
  # doesn't distinguish executable files from nonexecutable files.
62
66
  # See at: https://github.com/ruby/ruby/blob/ruby_2_4/file.c#L5362