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
@@ -5,7 +5,7 @@ module RuboCop
5
5
  module Style
6
6
  # Looks for uses of Perl-style regexp match
7
7
  # backreferences and their English versions like
8
- # $1, $2, $&, &+, $MATCH, $PREMATCH, etc.
8
+ # $1, $2, $&, $MATCH, $PREMATCH, etc.
9
9
  #
10
10
  # @example
11
11
  # # bad
@@ -69,6 +69,10 @@ module RuboCop
69
69
  # @return [String, nil]
70
70
  def preferred_expression_to(node)
71
71
  first = node.to_a.first
72
+ # NOTE: `$+` / `$LAST_PAREN_MATCH` is deliberately not converted. It
73
+ # refers to the last group that actually matched, which has no concise
74
+ # `Regexp.last_match` equivalent (`Regexp.last_match(-1)` is the last
75
+ # group in the pattern, which may be `nil`).
72
76
  case first
73
77
  when ::Integer
74
78
  "Regexp.last_match(#{first})"
@@ -78,8 +82,6 @@ module RuboCop
78
82
  'Regexp.last_match.pre_match'
79
83
  when :$', :$POSTMATCH
80
84
  'Regexp.last_match.post_match'
81
- when :$+, :$LAST_PAREN_MATCH
82
- 'Regexp.last_match(-1)'
83
85
  end
84
86
  end
85
87
 
@@ -38,6 +38,10 @@ module RuboCop
38
38
 
39
39
  def fix_exploded(node)
40
40
  exploded?(node) do |command, message|
41
+ # `raise RuntimeError, nil` uses the class name as the message, so
42
+ # rewriting it to `raise nil.to_s` (an empty message) would change it.
43
+ next if message.nil_type?
44
+
41
45
  add_offense(node, message: MSG_1) do |corrector|
42
46
  corrector.replace(node, replaced_exploded(node, command, message))
43
47
  end
@@ -56,6 +60,8 @@ module RuboCop
56
60
 
57
61
  def fix_compact(node)
58
62
  compact?(node) do |new_call, message|
63
+ next if message.nil_type?
64
+
59
65
  add_offense(node, message: MSG_2) do |corrector|
60
66
  corrector.replace(new_call, replaced_compact(message))
61
67
  end
@@ -62,7 +62,7 @@ module RuboCop
62
62
  def_node_matcher :select_predicate?, <<~PATTERN
63
63
  (call
64
64
  {
65
- (block $(call _ {:select :filter :find_all}) ...)
65
+ (any_block $(call _ {:select :filter :find_all}) ...)
66
66
  $(call _ {:select :filter :find_all} block_pass_type?)
67
67
  }
68
68
  ${:#{RESTRICT_ON_SEND.join(' :')}})
@@ -89,6 +89,8 @@ module RuboCop
89
89
 
90
90
  def on_send(node)
91
91
  format_without_additional_args?(node) do |value|
92
+ next if string_with_format_sequence?(value)
93
+
92
94
  replacement = escape_control_chars(value.source)
93
95
 
94
96
  add_offense(node, message: message(node, replacement)) do |corrector|
@@ -102,12 +104,31 @@ module RuboCop
102
104
 
103
105
  private
104
106
 
107
+ # A single-argument `format` whose string still contains a format sequence is
108
+ # not redundant: `format('%s')` raises at runtime, and `format('%%')` returns
109
+ # `'%'`, so replacing it with the literal would change behavior.
110
+ def string_with_format_sequence?(node)
111
+ string = static_string_value(node)
112
+ return false unless string
113
+
114
+ RuboCop::Cop::Utils::FormatString.new(string).format_sequences.any?
115
+ end
116
+
117
+ def static_string_value(node)
118
+ if node.str_type?
119
+ node.value
120
+ elsif node.dstr_type?
121
+ node.children.select(&:str_type?).map(&:value).join
122
+ end
123
+ end
124
+
105
125
  def message(node, prefer)
106
126
  format(MSG, prefer: prefer, method_name: node.method_name)
107
127
  end
108
128
 
109
129
  def detect_unnecessary_fields(node)
110
130
  return unless node.first_argument&.str_type?
131
+ return if node.first_argument.heredoc?
111
132
 
112
133
  string = node.first_argument.value
113
134
  arguments = node.arguments[1..]
@@ -245,6 +266,14 @@ module RuboCop
245
266
  def argument_value(argument)
246
267
  argument = argument.children.first if argument.begin_type?
247
268
 
269
+ # `nil` formats to an empty string (`format('%s', nil) == ''`), not the
270
+ # literal `'nil'` that `argument.source` would return.
271
+ return if argument.nil_type?
272
+
273
+ typed_argument_value(argument)
274
+ end
275
+
276
+ def typed_argument_value(argument)
248
277
  if argument.dsym_type?
249
278
  dsym_value(argument)
250
279
  elsif argument.hash_type?
@@ -165,11 +165,11 @@ module RuboCop
165
165
  end
166
166
 
167
167
  def inspect_end_of_ruby_code_line_continuation
168
- last_line = processed_source.lines[processed_source.ast.last_line - 1]
168
+ last_line_number = processed_source.ast.last_line
169
+ last_line = processed_source.lines[last_line_number - 1]
169
170
  return unless code_ends_with_continuation?(last_line)
170
171
 
171
- last_column = last_line.length
172
- line_continuation_range = range_between(last_column - 1, last_column)
172
+ line_continuation_range = trailing_line_continuation_range(last_line_number)
173
173
 
174
174
  add_offense(line_continuation_range) do |corrector|
175
175
  corrector.remove_trailing(line_continuation_range, 1)
@@ -182,6 +182,14 @@ module RuboCop
182
182
  last_line.end_with?(LINE_CONTINUATION)
183
183
  end
184
184
 
185
+ # The backslash is the last character of the line; locate it by the line's
186
+ # position in the buffer rather than treating the column as an absolute offset
187
+ # (which corrupts an earlier line in multi-line files).
188
+ def trailing_line_continuation_range(line_number)
189
+ line_range = processed_source.buffer.line_range(line_number)
190
+ range_between(line_range.end_pos - 1, line_range.end_pos)
191
+ end
192
+
185
193
  def inside_string_literal?(range, token)
186
194
  ALLOWED_STRING_TOKENS.include?(token.type) && token.pos.overlaps?(range)
187
195
  end
@@ -65,7 +65,7 @@ module RuboCop
65
65
  # different versions of Ruby so that e.g. /\i/ != /i/
66
66
  return true if /[[:alnum:]]/.match?(char)
67
67
  return true if ALLOWED_ALWAYS_ESCAPES.include?(char) || delimiter?(node, char)
68
- return true if requires_escape_to_avoid_interpolation?(node.source[index], char)
68
+ return true if requires_escape_to_avoid_interpolation?(node, index, char)
69
69
 
70
70
  if within_character_class
71
71
  ALLOWED_WITHIN_CHAR_CLASS_METACHAR_ESCAPES.include?(char) &&
@@ -97,10 +97,14 @@ module RuboCop
97
97
  delimiters.include?(char)
98
98
  end
99
99
 
100
- def requires_escape_to_avoid_interpolation?(char_before_escape, escaped_char)
100
+ def requires_escape_to_avoid_interpolation?(node, index, escaped_char)
101
101
  # Preserve escapes after '#' that would otherwise trigger interpolation:
102
- # '#@ivar', '#@@cvar', and '#$gvar'.
103
- char_before_escape == '#' && INTERPOLATION_SIGILS.include?(escaped_char)
102
+ # '#@ivar', '#@@cvar', and '#$gvar'. `index` is relative to the regexp
103
+ # contents, so index into those rather than `node.source` (which also
104
+ # includes the opening delimiter, e.g. `%r{`).
105
+ return false unless index.positive? && INTERPOLATION_SIGILS.include?(escaped_char)
106
+
107
+ contents_range(node).source[index - 1] == '#'
104
108
  end
105
109
 
106
110
  def each_escape(node)
@@ -100,6 +100,15 @@ module RuboCop
100
100
  add_lhs_to_local_variables_scopes(node.rhs, node.lhs)
101
101
  end
102
102
 
103
+ # Register the exception variable of `rescue => e` so that `self.e` in the
104
+ # body is not treated as redundant (it disambiguates the local variable).
105
+ def on_resbody(node)
106
+ exception_variable = node.exception_variable
107
+ return unless exception_variable&.lvasgn_type?
108
+
109
+ @local_variables_scopes[node] << exception_variable.name
110
+ end
111
+
103
112
  def on_in_pattern(node)
104
113
  add_match_var_scopes(node)
105
114
  end
@@ -100,12 +100,31 @@ module RuboCop
100
100
  end
101
101
 
102
102
  def range(redundant_keyword_init)
103
- if redundant_keyword_init.parent.left_siblings.last.is_a?(AST::Node)
104
- beginning_of_range = redundant_keyword_init.parent.left_siblings.last.source_range.end
103
+ if redundant_keyword_init.parent.pairs.all? { |pair| keyword_init?(pair) }
104
+ # The hash holds only `keyword_init` pairs, so it is emptied; anchor the
105
+ # removal before the hash to also drop the comma that precedes it.
106
+ range_emptying_hash(redundant_keyword_init)
107
+ else
108
+ # Other pairs remain, so just drop this pair and one adjacent comma.
109
+ range_within_hash(redundant_keyword_init)
110
+ end
111
+ end
112
+
113
+ def range_within_hash(pair)
114
+ if (left = pair.left_sibling)
115
+ left.source_range.end.join(pair.source_range.end)
116
+ elsif (right = pair.right_sibling)
117
+ pair.source_range.begin.join(right.source_range.begin)
118
+ else
119
+ pair.source_range
120
+ end
121
+ end
105
122
 
106
- beginning_of_range.join(redundant_keyword_init.source_range.end)
123
+ def range_emptying_hash(pair)
124
+ if (preceding = pair.parent.left_sibling).is_a?(AST::Node)
125
+ preceding.source_range.end.join(pair.source_range.end)
107
126
  else
108
- redundant_keyword_init
127
+ pair.source_range
109
128
  end
110
129
  end
111
130
  end
@@ -128,7 +128,7 @@ module RuboCop
128
128
 
129
129
  add_offense(range) do |corrector|
130
130
  if after_expression
131
- corrector.replace(range, "\n")
131
+ replace_semicolon_with_line_break(corrector, range)
132
132
  else
133
133
  # Prevents becoming one range instance with subsequent line when endless range
134
134
  # without parentheses.
@@ -148,6 +148,21 @@ module RuboCop
148
148
  end
149
149
  # rubocop:enable Metrics/AbcSize, Metrics/MethodLength
150
150
 
151
+ def replace_semicolon_with_line_break(corrector, range)
152
+ # Replacing the semicolon with a newline would move the rest of the
153
+ # line into the body of a heredoc opened earlier on that line.
154
+ return if heredoc_opened_before_semicolon?(range)
155
+
156
+ corrector.replace(range, "\n")
157
+ end
158
+
159
+ def heredoc_opened_before_semicolon?(semicolon_range)
160
+ processed_source.ast.each_descendant(:any_str).select(&:heredoc?).any? do |heredoc|
161
+ heredoc.first_line == semicolon_range.line &&
162
+ heredoc.source_range.end_pos <= semicolon_range.begin_pos
163
+ end
164
+ end
165
+
151
166
  def expressions_per_line(exprs)
152
167
  # create a map matching lines to the number of expressions on them
153
168
  exprs_lines = exprs.map(&:last_line)
@@ -155,10 +170,10 @@ module RuboCop
155
170
  end
156
171
 
157
172
  def find_semicolon_positions(line)
158
- # Scan for all the semicolons on the line
159
- semicolons = processed_source[line - 1].enum_for(:scan, ';')
160
- semicolons.each do
161
- yield Regexp.last_match.begin(0)
173
+ # Scan for all the semicolon tokens on the line. Iterating tokens rather
174
+ # than the raw source skips `;` characters inside string/regexp literals.
175
+ processed_source.tokens.each do |token|
176
+ yield token.column if token.line == line && token.semicolon?
162
177
  end
163
178
  end
164
179
 
@@ -44,11 +44,12 @@ module RuboCop
44
44
  add_offense(node) do |corrector|
45
45
  corrector.insert_after(do_line(node), "\n")
46
46
 
47
- node_body = node.body
48
-
49
- if node_body.respond_to?(:heredoc?) && node_body.heredoc?
47
+ if (heredoc = trailing_heredoc(node.body))
48
+ # The heredoc body extends past the `end` on the source, so the
49
+ # `end` has to be moved after it rather than before, which would
50
+ # otherwise move it into the heredoc body and break the syntax.
50
51
  corrector.remove(node.loc.end)
51
- corrector.insert_after(node_body.loc.heredoc_end, "\nend")
52
+ corrector.insert_after(heredoc.loc.heredoc_end, "\nend")
52
53
  else
53
54
  corrector.insert_before(node.loc.end, "\n")
54
55
  end
@@ -60,6 +61,18 @@ module RuboCop
60
61
 
61
62
  private
62
63
 
64
+ # Returns the heredoc opened on the block's line whose body extends the
65
+ # furthest down, whether it is the block body itself or nested within it.
66
+ def trailing_heredoc(node_body)
67
+ return unless node_body
68
+
69
+ heredocs = [node_body, *node_body.each_descendant].select do |node|
70
+ node.respond_to?(:heredoc?) && node.heredoc?
71
+ end
72
+
73
+ heredocs.max_by { |heredoc| heredoc.loc.heredoc_end.line }
74
+ end
75
+
63
76
  def do_line(node)
64
77
  if node.type?(:numblock, :itblock) ||
65
78
  node.arguments.children.empty? || node.send_node.lambda_literal?
@@ -41,6 +41,7 @@ module RuboCop
41
41
 
42
42
  def on_pair(node)
43
43
  return unless string_hash_key?(node)
44
+ return if node.key.heredoc?
44
45
 
45
46
  key_content = node.key.str_content
46
47
  return unless key_content.valid_encoding?
@@ -113,6 +113,10 @@ module RuboCop
113
113
  def offense?(node)
114
114
  condition = node.condition
115
115
 
116
+ # A modifier `if`/`unless` requires the parentheses, e.g. `(a if b) ? x : y`,
117
+ # so removing them would change the meaning. Don't flag it.
118
+ return false if parenthesized_modifier_condition?(condition)
119
+
116
120
  if safe_assignment?(condition)
117
121
  !safe_assignment_allowed?
118
122
  else
@@ -174,6 +178,13 @@ module RuboCop
174
178
  condition.children.any? { |child| below_ternary_precedence?(child) }
175
179
  end
176
180
 
181
+ def parenthesized_modifier_condition?(condition)
182
+ return false unless condition.begin_type?
183
+
184
+ inner = condition.children.first
185
+ inner&.if_type? && inner.modifier_form?
186
+ end
187
+
177
188
  def unparenthesized_method_call?(child)
178
189
  /^[a-z]/i.match?(method_name(child)) && !child.parenthesized?
179
190
  end
@@ -113,9 +113,7 @@ module RuboCop
113
113
 
114
114
  return unless first_offense
115
115
 
116
- if unused_variables_only?(first_offense, variables)
117
- return unused_range(node.type, mlhs_node, node.rhs)
118
- end
116
+ return unused_range(node, mlhs_node) if unused_variables_only?(first_offense, variables)
119
117
 
120
118
  return range_for_parentheses(first_offense, mlhs_node) if Util.parentheses?(mlhs_node)
121
119
 
@@ -130,13 +128,14 @@ module RuboCop
130
128
  offense.source_range == variables.first.source_range
131
129
  end
132
130
 
133
- def unused_range(node_type, mlhs_node, right)
131
+ def unused_range(node, mlhs_node)
134
132
  start_range = mlhs_node.source_range.begin_pos
135
133
 
136
- end_range = case node_type
137
- when :masgn
138
- right.source_range.begin_pos
139
- when :mlhs
134
+ # `node` can be an `mlhs` when recursing into a nested destructuring
135
+ # group; only a `masgn` has a right-hand side to anchor against.
136
+ end_range = if node.masgn_type?
137
+ node.rhs.source_range.begin_pos
138
+ else
140
139
  mlhs_node.source_range.end_pos
141
140
  end
142
141
 
@@ -33,6 +33,7 @@ module RuboCop
33
33
 
34
34
  def on_while(node)
35
35
  return unless node.multiline? && node.do?
36
+ return if same_line_body?(node)
36
37
 
37
38
  add_offense(node.loc.begin, message: format(MSG, keyword: node.keyword)) do |corrector|
38
39
  do_range = node.condition.source_range.end.join(node.loc.begin)
@@ -41,6 +42,12 @@ module RuboCop
41
42
  end
42
43
  end
43
44
  alias on_until on_while
45
+
46
+ private
47
+
48
+ def same_line_body?(node)
49
+ node.body && same_line?(node.loc.begin, node.body)
50
+ end
44
51
  end
45
52
  end
46
53
  end
@@ -93,6 +93,7 @@ module RuboCop
93
93
 
94
94
  def on_array(node)
95
95
  if bracketed_array_of?(:str, node)
96
+ return if node.values.any?(&:heredoc?)
96
97
  return if complex_content?(node.values)
97
98
  return if within_matrix_of_complex_content?(node)
98
99
 
@@ -9,8 +9,10 @@ module RuboCop
9
9
  # `receiver.length < 1` and `receiver.size == 0` that can be
10
10
  # replaced by `receiver.empty?` and `!receiver.empty?`.
11
11
  #
12
- # NOTE: `File`, `Tempfile`, and `StringIO` do not have `empty?`
13
- # so allow `size == 0` and `size.zero?`.
12
+ # NOTE: `File`, `Tempfile`, `StringIO`, and `File::Stat` do not have `empty?`
13
+ # so allow `size == 0` and `size.zero?`. Note that when a `File::Stat` object
14
+ # is stored in a variable (e.g. `stat = File.stat(path); stat.size.zero?`),
15
+ # the cop cannot detect the type and may still register a false positive.
14
16
  #
15
17
  # @safety
16
18
  # This cop is unsafe because it cannot be guaranteed that the receiver
@@ -146,7 +148,8 @@ module RuboCop
146
148
  # @!method non_polymorphic_collection?(node)
147
149
  def_node_matcher :non_polymorphic_collection?, <<~PATTERN
148
150
  {(send (send (send (const {nil? cbase} :File) :stat _) ...) ...)
149
- (send (send (send (const {nil? cbase} {:File :Tempfile :StringIO}) {:new :open} ...) ...) ...)}
151
+ (send (send (send (const {nil? cbase} {:File :Tempfile :StringIO}) {:new :open} ...) ...) ...)
152
+ (send (send (send (const (const {nil? cbase} :File) :Stat) :new ...) ...) ...)}
150
153
  PATTERN
151
154
  end
152
155
  end
@@ -91,12 +91,9 @@ module RuboCop
91
91
  command = 'rubocop --auto-gen-config'
92
92
 
93
93
  command += ' --auto-gen-only-exclude' if @options[:auto_gen_only_exclude]
94
-
95
- if no_exclude_limit?
96
- command += ' --no-exclude-limit'
97
- elsif @exclude_limit_option
98
- command += format(' --exclude-limit %<limit>d', limit: Integer(@exclude_limit_option))
99
- end
94
+ command += ' --disable-pending-cops' if @options[:disable_pending_cops]
95
+ command += ' --enable-pending-cops' if @options[:enable_pending_cops]
96
+ command += exclude_limit_option
100
97
  command += ' --no-offense-counts' unless show_offense_counts?
101
98
 
102
99
  command += ' --no-auto-gen-timestamp' unless show_timestamp?
@@ -106,6 +103,16 @@ module RuboCop
106
103
  command
107
104
  end
108
105
 
106
+ def exclude_limit_option
107
+ if no_exclude_limit?
108
+ ' --no-exclude-limit'
109
+ elsif @exclude_limit_option
110
+ format(' --exclude-limit %<limit>d', limit: Integer(@exclude_limit_option))
111
+ else
112
+ ''
113
+ end
114
+ end
115
+
109
116
  def timestamp
110
117
  show_timestamp? ? "on #{Time.now.utc} " : ''
111
118
  end
@@ -158,7 +165,7 @@ module RuboCop
158
165
  output_buffer.puts "# Offense count: #{offense_count}" if show_offense_counts?
159
166
 
160
167
  cop_class = Cop::Registry.global.find_by_cop_name(cop_name)
161
- default_cfg = default_config(cop_name)
168
+ default_cfg = default_config(cop_name) || @config_for_pwd[cop_name]
162
169
 
163
170
  if supports_safe_autocorrect?(cop_class, default_cfg)
164
171
  output_buffer.puts '# This cop supports safe autocorrection (--autocorrect).'
@@ -120,7 +120,6 @@ module RuboCop
120
120
  file_iterator(files) do |file|
121
121
  offenses = process_file(file)
122
122
  succeeded = offenses.none? { |o| considered_failure?(o) && offense_displayed?(o) }
123
- raise Parallel::Break if @options[:fail_fast] && !succeeded
124
123
 
125
124
  [offenses, succeeded]
126
125
  end
@@ -211,8 +210,11 @@ module RuboCop
211
210
  on_start.call(file, index)
212
211
  result = yield file
213
212
  on_finish.call(file, index, result)
214
- rescue Parallel::Break
215
- break
213
+
214
+ # Report and count the offending file before stopping so `--fail-fast`
215
+ # still shows its offenses and exits with a failing status.
216
+ _offenses, succeeded = result
217
+ break if @options[:fail_fast] && !succeeded
216
218
  end
217
219
  end
218
220
 
@@ -57,6 +57,12 @@ module RuboCop
57
57
  end
58
58
 
59
59
  Process.waitpid(pid)
60
+
61
+ # The daemon writes its pid file asynchronously after forking, so wait until
62
+ # the server is actually running before returning. This prevents a race where
63
+ # a subsequent command (e.g. `--restart-server`) observes an inconsistent
64
+ # state right after `--start-server` returns.
65
+ Server.wait_for_running_status!(true)
60
66
  end
61
67
 
62
68
  def write_port_and_token_files
@@ -3,7 +3,7 @@
3
3
  module RuboCop
4
4
  # This module holds the RuboCop version information.
5
5
  module Version
6
- STRING = '1.87.0'
6
+ STRING = '1.88.1'
7
7
 
8
8
  MSG = '%<version>s (using %<parser_version>s, ' \
9
9
  'rubocop-ast %<rubocop_ast_version>s, ' \
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rubocop
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.87.0
4
+ version: 1.88.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Bozhidar Batsov
@@ -1119,9 +1119,9 @@ licenses:
1119
1119
  - MIT
1120
1120
  metadata:
1121
1121
  homepage_uri: https://rubocop.org/
1122
- changelog_uri: https://github.com/rubocop/rubocop/releases/tag/v1.87.0
1122
+ changelog_uri: https://github.com/rubocop/rubocop/releases/tag/v1.88.1
1123
1123
  source_code_uri: https://github.com/rubocop/rubocop/
1124
- documentation_uri: https://docs.rubocop.org/rubocop/1.87/
1124
+ documentation_uri: https://docs.rubocop.org/rubocop/1.88/
1125
1125
  bug_tracker_uri: https://github.com/rubocop/rubocop/issues
1126
1126
  rubygems_mfa_required: 'true'
1127
1127
  rdoc_options: []