rubocop 1.79.2 → 1.81.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  3. data/config/default.yml +10 -0
  4. data/exe/rubocop +1 -8
  5. data/lib/rubocop/cli/command/auto_generate_config.rb +2 -2
  6. data/lib/rubocop/cli.rb +6 -2
  7. data/lib/rubocop/config_loader.rb +3 -1
  8. data/lib/rubocop/config_store.rb +5 -0
  9. data/lib/rubocop/cop/autocorrect_logic.rb +2 -2
  10. data/lib/rubocop/cop/correctors/alignment_corrector.rb +7 -4
  11. data/lib/rubocop/cop/correctors/for_to_each_corrector.rb +7 -2
  12. data/lib/rubocop/cop/internal_affairs/node_pattern_groups.rb +3 -1
  13. data/lib/rubocop/cop/internal_affairs/on_send_without_on_csend.rb +1 -1
  14. data/lib/rubocop/cop/layout/class_structure.rb +1 -1
  15. data/lib/rubocop/cop/layout/dot_position.rb +1 -1
  16. data/lib/rubocop/cop/layout/empty_line_between_defs.rb +30 -12
  17. data/lib/rubocop/cop/layout/empty_lines_after_module_inclusion.rb +1 -1
  18. data/lib/rubocop/cop/layout/line_length.rb +9 -1
  19. data/lib/rubocop/cop/layout/multiline_operation_indentation.rb +8 -4
  20. data/lib/rubocop/cop/layout/rescue_ensure_alignment.rb +8 -0
  21. data/lib/rubocop/cop/layout/trailing_whitespace.rb +1 -1
  22. data/lib/rubocop/cop/lint/duplicate_regexp_character_class_element.rb +5 -42
  23. data/lib/rubocop/cop/lint/missing_cop_enable_directive.rb +1 -2
  24. data/lib/rubocop/cop/lint/self_assignment.rb +5 -4
  25. data/lib/rubocop/cop/lint/shadowed_argument.rb +7 -7
  26. data/lib/rubocop/cop/lint/uri_escape_unescape.rb +2 -0
  27. data/lib/rubocop/cop/lint/void.rb +7 -0
  28. data/lib/rubocop/cop/message_annotator.rb +1 -1
  29. data/lib/rubocop/cop/mixin/check_line_breakable.rb +1 -1
  30. data/lib/rubocop/cop/mixin/end_keyword_alignment.rb +1 -7
  31. data/lib/rubocop/cop/mixin/trailing_comma.rb +1 -1
  32. data/lib/rubocop/cop/naming/method_name.rb +1 -1
  33. data/lib/rubocop/cop/naming/predicate_method.rb +15 -2
  34. data/lib/rubocop/cop/style/array_intersect.rb +45 -11
  35. data/lib/rubocop/cop/style/array_intersect_with_single_element.rb +47 -0
  36. data/lib/rubocop/cop/style/bitwise_predicate.rb +8 -1
  37. data/lib/rubocop/cop/style/double_negation.rb +1 -1
  38. data/lib/rubocop/cop/style/explicit_block_argument.rb +1 -1
  39. data/lib/rubocop/cop/style/hash_syntax.rb +1 -1
  40. data/lib/rubocop/cop/style/infinite_loop.rb +1 -1
  41. data/lib/rubocop/cop/style/it_block_parameter.rb +1 -1
  42. data/lib/rubocop/cop/style/nil_comparison.rb +9 -7
  43. data/lib/rubocop/cop/style/numbered_parameters.rb +1 -1
  44. data/lib/rubocop/cop/style/redundant_begin.rb +34 -0
  45. data/lib/rubocop/cop/style/redundant_condition.rb +1 -1
  46. data/lib/rubocop/cop/style/redundant_exception.rb +1 -1
  47. data/lib/rubocop/cop/style/redundant_format.rb +18 -3
  48. data/lib/rubocop/cop/style/redundant_parentheses.rb +14 -11
  49. data/lib/rubocop/cop/style/redundant_regexp_argument.rb +4 -0
  50. data/lib/rubocop/cop/style/redundant_regexp_escape.rb +8 -0
  51. data/lib/rubocop/cop/style/safe_navigation.rb +18 -1
  52. data/lib/rubocop/cop/style/string_concatenation.rb +17 -13
  53. data/lib/rubocop/cop/style/symbol_array.rb +1 -1
  54. data/lib/rubocop/cop/style/trailing_comma_in_arguments.rb +45 -0
  55. data/lib/rubocop/cop/style/unless_else.rb +10 -9
  56. data/lib/rubocop/cop/utils/format_string.rb +10 -0
  57. data/lib/rubocop/cop/variable_force/variable.rb +1 -1
  58. data/lib/rubocop/cop/variable_force.rb +9 -7
  59. data/lib/rubocop/formatter/disabled_config_formatter.rb +18 -5
  60. data/lib/rubocop/lsp/diagnostic.rb +21 -20
  61. data/lib/rubocop/lsp/routes.rb +62 -6
  62. data/lib/rubocop/lsp/runtime.rb +2 -2
  63. data/lib/rubocop/lsp/server.rb +2 -2
  64. data/lib/rubocop/lsp/stdin_runner.rb +0 -16
  65. data/lib/rubocop/result_cache.rb +1 -1
  66. data/lib/rubocop/runner.rb +6 -4
  67. data/lib/rubocop/target_finder.rb +9 -9
  68. data/lib/rubocop/target_ruby.rb +10 -1
  69. data/lib/rubocop/version.rb +1 -1
  70. data/lib/rubocop.rb +1 -0
  71. data/lib/ruby_lsp/rubocop/addon.rb +23 -8
  72. data/lib/ruby_lsp/rubocop/runtime_adapter.rb +49 -15
  73. metadata +11 -7
@@ -19,8 +19,7 @@ module RuboCop
19
19
  def check_end_kw_alignment(node, align_ranges)
20
20
  return if ignored_node?(node)
21
21
 
22
- end_loc = node.loc.end
23
- return if accept_end_kw_alignment?(end_loc)
22
+ return unless (end_loc = node.loc.end)
24
23
 
25
24
  matching = matching_ranges(end_loc, align_ranges)
26
25
 
@@ -57,11 +56,6 @@ module RuboCop
57
56
  add_offense(end_loc, message: msg) { |corrector| autocorrect(corrector, node) }
58
57
  end
59
58
 
60
- def accept_end_kw_alignment?(end_loc)
61
- end_loc.nil? || # Discard modifier forms of if/while/until.
62
- !/\A[ \t]*end/.match?(processed_source.lines[end_loc.line - 1])
63
- end
64
-
65
59
  def style_parameter_name
66
60
  'EnforcedStyleAlignWith'
67
61
  end
@@ -140,7 +140,7 @@ module RuboCop
140
140
  end
141
141
 
142
142
  def last_item_precedes_newline?(node)
143
- after_last_item = node.children.last.source_range.end.join(node.loc.end.begin)
143
+ after_last_item = node.children.last.source_range.end.join(node.source_range.end)
144
144
 
145
145
  after_last_item.source.start_with?(/,?\s*(#.*)?\n/)
146
146
  end
@@ -198,7 +198,7 @@ module RuboCop
198
198
 
199
199
  if forbidden_name?(name.to_s)
200
200
  register_forbidden_name(node)
201
- elsif !OPERATOR_METHODS.include?(name)
201
+ elsif !OPERATOR_METHODS.include?(name.to_sym)
202
202
  check_name(node, name, range_position(node))
203
203
  end
204
204
  end
@@ -14,7 +14,7 @@ module RuboCop
14
14
  # method calls are assumed to return boolean values. The cop does not make an assessment
15
15
  # if the return type is unknown (non-predicate method calls, variables, etc.).
16
16
  #
17
- # NOTE: Operator methods (`def ==`, etc.) are ignored.
17
+ # NOTE: The `initialize` method and operator methods (`def ==`, etc.) are ignored.
18
18
  #
19
19
  # By default, the cop runs in `conservative` mode, which allows a method to be named
20
20
  # with a question mark as long as at least one return value is boolean. In `aggressive`
@@ -113,6 +113,18 @@ module RuboCop
113
113
  # true
114
114
  # end
115
115
  #
116
+ # @example AllowedMethods: [call] (default)
117
+ # # good
118
+ # def call
119
+ # foo == bar
120
+ # end
121
+ #
122
+ # @example AllowedPatterns: [\Afoo]
123
+ # # good
124
+ # def foo?
125
+ # 'foo'
126
+ # end
127
+ #
116
128
  # @example AllowBangMethods: false (default)
117
129
  # # bad
118
130
  # def save!
@@ -149,7 +161,8 @@ module RuboCop
149
161
  private
150
162
 
151
163
  def allowed?(node)
152
- allowed_method?(node.method_name) ||
164
+ node.method?(:initialize) ||
165
+ allowed_method?(node.method_name) ||
153
166
  matches_allowed_pattern?(node.method_name) ||
154
167
  allowed_bang_method?(node) ||
155
168
  node.operator_method? ||
@@ -10,6 +10,8 @@ module RuboCop
10
10
  # * `(array1 & array2).any?`
11
11
  # * `(array1.intersection(array2)).any?`
12
12
  # * `array1.any? { |elem| array2.member?(elem) }`
13
+ # * `(array1 & array2).count > 0`
14
+ # * `(array1 & array2).size > 0`
13
15
  #
14
16
  # can be replaced with `array1.intersect?(array2)`.
15
17
  #
@@ -51,6 +53,19 @@ module RuboCop
51
53
  # array1.intersect?(array2)
52
54
  # !array1.intersect?(array2)
53
55
  #
56
+ # # bad
57
+ # (array1 & array2).count > 0
58
+ # (array1 & array2).count.positive?
59
+ # (array1 & array2).count != 0
60
+ #
61
+ # (array1 & array2).count == 0
62
+ # (array1 & array2).count.zero?
63
+ #
64
+ # # good
65
+ # array1.intersect?(array2)
66
+ #
67
+ # !array1.intersect?(array2)
68
+ #
54
69
  # @example AllCops:ActiveSupportExtensionsEnabled: false (default)
55
70
  # # good
56
71
  # (array1 & array2).present?
@@ -73,9 +88,11 @@ module RuboCop
73
88
  PREDICATES = %i[any? empty? none?].to_set.freeze
74
89
  ACTIVE_SUPPORT_PREDICATES = (PREDICATES + %i[present? blank?]).freeze
75
90
 
91
+ ARRAY_SIZE_METHODS = %i[count length size].to_set.freeze
92
+
76
93
  # @!method bad_intersection_check?(node, predicates)
77
94
  def_node_matcher :bad_intersection_check?, <<~PATTERN
78
- (call
95
+ $(call
79
96
  {
80
97
  (begin (send $_ :& $_))
81
98
  (call $_ :intersection $_)
@@ -84,6 +101,20 @@ module RuboCop
84
101
  )
85
102
  PATTERN
86
103
 
104
+ # @!method intersection_size_check?(node, predicates)
105
+ def_node_matcher :intersection_size_check?, <<~PATTERN
106
+ (call
107
+ $(call
108
+ {
109
+ (begin (send $_ :& $_))
110
+ (call $_ :intersection $_)
111
+ }
112
+ %ARRAY_SIZE_METHODS
113
+ )
114
+ {$:> (int 0) | $:positive? | $:!= (int 0) | $:== (int 0) | $:zero?}
115
+ )
116
+ PATTERN
117
+
87
118
  # @!method any_none_block_intersection(node)
88
119
  def_node_matcher :any_none_block_intersection, <<~PATTERN
89
120
  {
@@ -104,15 +135,15 @@ module RuboCop
104
135
  PATTERN
105
136
 
106
137
  MSG = 'Use `%<replacement>s` instead of `%<existing>s`.'
107
- STRAIGHT_METHODS = %i[present? any?].freeze
108
- NEGATED_METHODS = %i[blank? empty? none?].freeze
138
+ STRAIGHT_METHODS = %i[present? any? > positive? !=].freeze
139
+ NEGATED_METHODS = %i[blank? empty? none? == zero?].freeze
109
140
  RESTRICT_ON_SEND = (STRAIGHT_METHODS + NEGATED_METHODS).freeze
110
141
 
111
142
  def on_send(node)
112
143
  return if node.block_literal?
113
- return unless (receiver, argument, method_name = bad_intersection?(node))
144
+ return unless (dot_node, receiver, argument, method_name = bad_intersection?(node))
114
145
 
115
- dot = node.loc.dot.source
146
+ dot = dot_node.loc.dot.source
116
147
  bang = straight?(method_name) ? '' : '!'
117
148
  replacement = "#{bang}#{receiver.source}#{dot}intersect?(#{argument.source})"
118
149
 
@@ -135,13 +166,16 @@ module RuboCop
135
166
  private
136
167
 
137
168
  def bad_intersection?(node)
138
- predicates = if active_support_extensions_enabled?
139
- ACTIVE_SUPPORT_PREDICATES
140
- else
141
- PREDICATES
142
- end
169
+ bad_intersection_check?(node, bad_intersection_predicates) ||
170
+ intersection_size_check?(node)
171
+ end
143
172
 
144
- bad_intersection_check?(node, predicates)
173
+ def bad_intersection_predicates
174
+ if active_support_extensions_enabled?
175
+ ACTIVE_SUPPORT_PREDICATES
176
+ else
177
+ PREDICATES
178
+ end
145
179
  end
146
180
 
147
181
  def straight?(method_name)
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Style
6
+ # Use `include?(element)` instead of `intersect?([element])`.
7
+ #
8
+ # @safety
9
+ # The receiver might not be an array.
10
+ #
11
+ # @example
12
+ # # bad
13
+ # array.intersect?([element])
14
+ #
15
+ # # good
16
+ # array.include?(element)
17
+ class ArrayIntersectWithSingleElement < Base
18
+ extend AutoCorrector
19
+
20
+ MSG = 'Use `include?(element)` instead of `intersect?([element])`.'
21
+
22
+ RESTRICT_ON_SEND = %i[intersect?].freeze
23
+
24
+ # @!method single_element(node)
25
+ def_node_matcher :single_element, <<~PATTERN
26
+ (send _ _ $(array $_))
27
+ PATTERN
28
+
29
+ def on_send(node)
30
+ array, element = single_element(node)
31
+ return unless array
32
+
33
+ add_offense(
34
+ node.source_range.with(begin_pos: node.loc.selector.begin_pos)
35
+ ) do |corrector|
36
+ corrector.replace(node.loc.selector, 'include?')
37
+ corrector.replace(
38
+ array,
39
+ array.percent_literal? ? element.value.inspect : element.source
40
+ )
41
+ end
42
+ end
43
+ alias on_csend on_send
44
+ end
45
+ end
46
+ end
47
+ end
@@ -70,18 +70,25 @@ module RuboCop
70
70
  (send _ :& _))
71
71
  PATTERN
72
72
 
73
+ # rubocop:disable Metrics/AbcSize
73
74
  def on_send(node)
74
75
  return unless node.receiver&.begin_type?
75
76
  return unless (preferred_method = preferred_method(node))
76
77
 
77
78
  bit_operation = node.receiver.children.first
78
79
  lhs, _operator, rhs = *bit_operation
79
- preferred = "#{lhs.source}.#{preferred_method}(#{rhs.source})"
80
+
81
+ preferred = if preferred_method == 'allbits?' && lhs.source == node.first_argument.source
82
+ "#{rhs.source}.allbits?(#{lhs.source})"
83
+ else
84
+ "#{lhs.source}.#{preferred_method}(#{rhs.source})"
85
+ end
80
86
 
81
87
  add_offense(node, message: format(MSG, preferred: preferred)) do |corrector|
82
88
  corrector.replace(node, preferred)
83
89
  end
84
90
  end
91
+ # rubocop:enable Metrics/AbcSize
85
92
 
86
93
  private
87
94
 
@@ -96,7 +96,7 @@ module RuboCop
96
96
  elsif last_child.type?(:pair, :hash) || last_child.parent.array_type?
97
97
  false
98
98
  else
99
- last_child.last_line <= node.last_line
99
+ last_child.first_line <= node.first_line
100
100
  end
101
101
  end
102
102
 
@@ -56,7 +56,7 @@ module RuboCop
56
56
 
57
57
  def initialize(config = nil, options = nil)
58
58
  super
59
- @def_nodes = Set.new
59
+ @def_nodes = Set.new.compare_by_identity
60
60
  end
61
61
 
62
62
  def on_yield(node)
@@ -212,7 +212,7 @@ module RuboCop
212
212
  end
213
213
 
214
214
  def word_symbol_pair?(pair)
215
- return false unless pair.key.type?(:sym, :dsym)
215
+ return false unless pair.key.any_sym_type?
216
216
 
217
217
  acceptable_19_syntax_symbol?(pair.key.source)
218
218
  end
@@ -68,7 +68,7 @@ module RuboCop
68
68
  end
69
69
 
70
70
  def autocorrect(corrector, node)
71
- if node.type?(:while_post, :until_post)
71
+ if node.post_condition_loop?
72
72
  replace_begin_end_with_modifier(corrector, node)
73
73
  elsif node.modifier_form?
74
74
  replace_source(corrector, node.source_range, modifier_replacement(node))
@@ -94,7 +94,7 @@ module RuboCop
94
94
  def on_itblock(node)
95
95
  case style
96
96
  when :allow_single_line
97
- return if node.single_line?
97
+ return if same_line?(node.source_range.begin, node.source_range.end)
98
98
 
99
99
  add_offense(node, message: MSG_AVOID_IT_PARAMETER_MULTILINE)
100
100
  when :disallow
@@ -43,24 +43,26 @@ module RuboCop
43
43
  # @!method nil_check?(node)
44
44
  def_node_matcher :nil_check?, '(send _ :nil?)'
45
45
 
46
+ # rubocop:disable Metrics/AbcSize
46
47
  def on_send(node)
47
48
  return unless node.receiver
48
49
 
49
50
  style_check?(node) do
50
51
  add_offense(node.loc.selector) do |corrector|
51
- new_code = if prefer_comparison?
52
- node.source.sub('.nil?', ' == nil')
53
- else
54
- node.source.sub(/\s*={2,3}\s*nil/, '.nil?')
55
- end
56
-
57
- corrector.replace(node, new_code)
52
+ if prefer_comparison?
53
+ range = node.loc.dot.join(node.loc.selector.end)
54
+ corrector.replace(range, ' == nil')
55
+ else
56
+ range = node.receiver.source_range.end.join(node.source_range.end)
57
+ corrector.replace(range, '.nil?')
58
+ end
58
59
 
59
60
  parent = node.parent
60
61
  corrector.wrap(node, '(', ')') if parent.respond_to?(:method?) && parent.method?(:!)
61
62
  end
62
63
  end
63
64
  end
65
+ # rubocop:enable Metrics/AbcSize
64
66
 
65
67
  private
66
68
 
@@ -36,7 +36,7 @@ module RuboCop
36
36
  def on_numblock(node)
37
37
  if style == :disallow
38
38
  add_offense(node, message: MSG_DISALLOW)
39
- elsif node.multiline?
39
+ elsif !same_line?(node.source_range.begin, node.source_range.end)
40
40
  add_offense(node, message: MSG_MULTI_LINE)
41
41
  end
42
42
  end
@@ -85,6 +85,29 @@ module RuboCop
85
85
  end
86
86
  alias on_defs on_def
87
87
 
88
+ def on_if(node)
89
+ return if node.modifier_form?
90
+
91
+ inspect_branches(node)
92
+ end
93
+
94
+ def on_case(node)
95
+ inspect_branches(node)
96
+ end
97
+ alias on_case_match on_case
98
+
99
+ def on_while(node)
100
+ return if node.modifier_form?
101
+
102
+ body = node.body
103
+
104
+ return unless body&.kwbegin_type?
105
+ return if body.rescue_node || body.ensure_node
106
+
107
+ register_offense(body)
108
+ end
109
+ alias on_until on_while
110
+
88
111
  def on_block(node)
89
112
  return if target_ruby_version < 2.5
90
113
  return if node.send_node.lambda_literal?
@@ -180,6 +203,8 @@ module RuboCop
180
203
  end
181
204
 
182
205
  def begin_block_has_multiline_statements?(node)
206
+ return false unless node.parent
207
+
183
208
  node.children.count >= 2
184
209
  end
185
210
 
@@ -199,6 +224,15 @@ module RuboCop
199
224
  def valid_begin_assignment?(node)
200
225
  node.parent&.assignment? && !node.children.one?
201
226
  end
227
+
228
+ def inspect_branches(node)
229
+ node.branches.each do |branch|
230
+ next unless branch&.kwbegin_type?
231
+ next if branch.rescue_node || branch.ensure_node
232
+
233
+ register_offense(branch)
234
+ end
235
+ end
202
236
  end
203
237
  end
204
238
  end
@@ -247,7 +247,7 @@ module RuboCop
247
247
  "#{if_branch.receiver.source} #{if_branch.method_name} (#{argument_source}"
248
248
  elsif if_branch.true_type?
249
249
  condition = if_branch.parent.condition
250
- return condition.source if condition.arguments.empty?
250
+ return condition.source if condition.arguments.empty? || condition.parenthesized?
251
251
 
252
252
  wrap_arguments_with_parens(condition)
253
253
  else
@@ -51,7 +51,7 @@ module RuboCop
51
51
  end
52
52
 
53
53
  def string_message?(message)
54
- message.type?(:str, :dstr, :xstr)
54
+ message.any_str_type?
55
55
  end
56
56
 
57
57
  def fix_compact(node)
@@ -134,6 +134,7 @@ module RuboCop
134
134
  end
135
135
  end
136
136
 
137
+ # rubocop:disable Metrics/CyclomaticComplexity
137
138
  def all_fields_literal?(string, arguments)
138
139
  count = 0
139
140
  sequences = RuboCop::Cop::Utils::FormatString.new(string).format_sequences
@@ -141,6 +142,7 @@ module RuboCop
141
142
 
142
143
  sequences.each do |sequence|
143
144
  next if sequence.percent?
145
+ next if unknown_variable_width?(sequence, arguments)
144
146
 
145
147
  hash = arguments.detect(&:hash_type?)
146
148
  next unless (argument = find_argument(sequence, arguments, hash))
@@ -151,19 +153,32 @@ module RuboCop
151
153
 
152
154
  sequences.size == count
153
155
  end
156
+ # rubocop:enable Metrics/CyclomaticComplexity
154
157
 
158
+ # If the sequence has a variable (`*`) width, it cannot be autocorrected
159
+ # if the width is not given as a numeric literal argument
160
+ def unknown_variable_width?(sequence, arguments)
161
+ return false unless sequence.variable_width?
162
+
163
+ argument = arguments[sequence.variable_width_argument_number - 1]
164
+ !numeric?(argument)
165
+ end
166
+
167
+ # rubocop:disable Metrics/AbcSize
155
168
  def find_argument(sequence, arguments, hash)
156
169
  if hash && (sequence.annotated? || sequence.template?)
157
170
  find_hash_value_node(hash, sequence.name.to_sym).first
171
+ elsif sequence.variable_width?
172
+ # If the specifier contains `*`, the argument for the width can be ignored.
173
+ arguments.delete_at(sequence.variable_width_argument_number - 1)
174
+ arguments.shift
158
175
  elsif sequence.arg_number
159
176
  arguments[sequence.arg_number.to_i - 1]
160
177
  else
161
- # If the specifier contains `*`, the following arguments will be used
162
- # to specify the width and can be ignored.
163
- (sequence.arity - 1).times { arguments.shift }
164
178
  arguments.shift
165
179
  end
166
180
  end
181
+ # rubocop:enable Metrics/AbcSize
167
182
 
168
183
  def matching_argument?(sequence, argument)
169
184
  # Template specifiers don't give a type, any acceptable literal type is ok.
@@ -24,9 +24,6 @@ module RuboCop
24
24
  (send `{(send _recv _msg) str array hash const #variable?} :[] ...)
25
25
  PATTERN
26
26
 
27
- # @!method method_node_and_args(node)
28
- def_node_matcher :method_node_and_args, '$(call _recv _msg $...)'
29
-
30
27
  # @!method rescue?(node)
31
28
  def_node_matcher :rescue?, '{^resbody ^^resbody}'
32
29
 
@@ -205,6 +202,7 @@ module RuboCop
205
202
  return false unless node.rescue_type?
206
203
  return false unless (parent = begin_node.parent)
207
204
  return false if parent.if_type? && parent.ternary?
205
+ return false if parent.conditional? && parent.condition == begin_node
208
206
 
209
207
  !parent.type?(:call, :array, :pair)
210
208
  end
@@ -220,7 +218,7 @@ module RuboCop
220
218
  end
221
219
 
222
220
  def call_node?(node)
223
- node.call_type? || (node.any_block_type? && !node.lambda_or_proc?)
221
+ node.call_type? || (node.any_block_type? && node.braces? && !node.lambda_or_proc?)
224
222
  end
225
223
 
226
224
  def check_send(begin_node, node)
@@ -228,7 +226,7 @@ module RuboCop
228
226
 
229
227
  return check_unary(begin_node, node) if node.unary_operation?
230
228
 
231
- return unless method_call_with_redundant_parentheses?(node)
229
+ return unless method_call_with_redundant_parentheses?(begin_node, node)
232
230
  return if call_chain_starts_with_int?(begin_node, node) ||
233
231
  do_end_block_in_method_chain?(begin_node, node)
234
232
 
@@ -239,8 +237,7 @@ module RuboCop
239
237
  return if begin_node.chained?
240
238
 
241
239
  node = node.children.first while suspect_unary?(node)
242
-
243
- return if node.send_type? && !method_call_with_redundant_parentheses?(node)
240
+ return unless method_call_with_redundant_parentheses?(begin_node, node)
244
241
 
245
242
  offense(begin_node, 'a unary operation')
246
243
  end
@@ -302,13 +299,19 @@ module RuboCop
302
299
  end
303
300
  end
304
301
 
305
- def method_call_with_redundant_parentheses?(node)
306
- return false unless node.call_type?
302
+ def method_call_with_redundant_parentheses?(begin_node, node)
303
+ return false unless node.type?(:call, :super, :yield, :defined?)
307
304
  return false if node.prefix_not?
305
+ return true if singular_parenthesized_parent?(begin_node)
306
+
307
+ node.arguments.empty? || parentheses?(node) || square_brackets?(node)
308
+ end
308
309
 
309
- send_node, args = method_node_and_args(node)
310
+ def singular_parenthesized_parent?(begin_node)
311
+ return true unless begin_node.parent
312
+ return false if begin_node.parent.type?(:splat, :kwsplat)
310
313
 
311
- args.empty? || parentheses?(send_node) || square_brackets?(send_node)
314
+ parentheses?(begin_node) && begin_node.parent.children.one?
312
315
  end
313
316
 
314
317
  def only_begin_arg?(args)
@@ -66,6 +66,7 @@ module RuboCop
66
66
  DETERMINISTIC_REGEX.match?(regexp_node.source)
67
67
  end
68
68
 
69
+ # rubocop:disable Metrics/MethodLength
69
70
  def preferred_argument(regexp_node)
70
71
  new_argument = replacement(regexp_node)
71
72
 
@@ -73,6 +74,8 @@ module RuboCop
73
74
  new_argument.gsub!("'", "\\\\'")
74
75
  new_argument.gsub!('\"', '"')
75
76
  quote = "'"
77
+ elsif new_argument.include?("\\'")
78
+ quote = "'"
76
79
  elsif new_argument.include?('\'')
77
80
  new_argument.gsub!("'", "\\\\'")
78
81
  quote = "'"
@@ -84,6 +87,7 @@ module RuboCop
84
87
 
85
88
  "#{quote}#{new_argument}#{quote}"
86
89
  end
90
+ # rubocop:enable Metrics/MethodLength
87
91
 
88
92
  def replacement(regexp_node)
89
93
  regexp_content = regexp_node.content
@@ -41,6 +41,7 @@ module RuboCop
41
41
  ALLOWED_ALWAYS_ESCAPES = " \n[]^\\#".chars.freeze
42
42
  ALLOWED_WITHIN_CHAR_CLASS_METACHAR_ESCAPES = '-'.chars.freeze
43
43
  ALLOWED_OUTSIDE_CHAR_CLASS_METACHAR_ESCAPES = '.*+?{}()|$'.chars.freeze
44
+ INTERPOLATION_SIGILS = %w[@ $].freeze
44
45
 
45
46
  def on_regexp(node)
46
47
  each_escape(node) do |char, index, within_character_class|
@@ -64,6 +65,7 @@ module RuboCop
64
65
  # different versions of Ruby so that e.g. /\i/ != /i/
65
66
  return true if /[[:alnum:]]/.match?(char)
66
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)
67
69
 
68
70
  if within_character_class
69
71
  ALLOWED_WITHIN_CHAR_CLASS_METACHAR_ESCAPES.include?(char) &&
@@ -95,6 +97,12 @@ module RuboCop
95
97
  delimiters.include?(char)
96
98
  end
97
99
 
100
+ def requires_escape_to_avoid_interpolation?(char_before_escape, escaped_char)
101
+ # Preserve escapes after '#' that would otherwise trigger interpolation:
102
+ # '#@ivar', '#@@cvar', and '#$gvar'.
103
+ char_before_escape == '#' && INTERPOLATION_SIGILS.include?(escaped_char)
104
+ end
105
+
98
106
  def each_escape(node)
99
107
  node.parsed_tree&.traverse&.reduce(0) do |char_class_depth, (event, expr)|
100
108
  yield(expr.text[1], expr.ts, !char_class_depth.zero?) if expr.type == :escape
@@ -142,6 +142,7 @@ module RuboCop
142
142
  # @!method strip_begin(node)
143
143
  def_node_matcher :strip_begin, '{ (begin $!begin) $!(begin) }'
144
144
 
145
+ # rubocop:disable Metrics/AbcSize
145
146
  def on_if(node)
146
147
  return if allowed_if_condition?(node)
147
148
 
@@ -155,9 +156,11 @@ module RuboCop
155
156
  removal_ranges = [begin_range(node, body), end_range(node, body)]
156
157
 
157
158
  report_offense(node, method_chain, method_call, *removal_ranges) do |corrector|
159
+ corrector.replace(receiver, checked_variable.source) if checked_variable.csend_type?
158
160
  corrector.insert_before(method_call.loc.dot, '&') unless method_call.safe_navigation?
159
161
  end
160
162
  end
163
+ # rubocop:enable Metrics/AbcSize
161
164
 
162
165
  def on_and(node) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength
163
166
  collect_and_clauses(node).each do |(lhs, lhs_operator_range), (rhs, _rhs_operator_range)|
@@ -259,8 +262,14 @@ module RuboCop
259
262
  end
260
263
 
261
264
  def dotless_operator_call?(method_call)
265
+ return true if dotless_operator_method?(method_call)
266
+
262
267
  method_call = method_call.parent while method_call.parent.send_type?
263
268
 
269
+ dotless_operator_method?(method_call)
270
+ end
271
+
272
+ def dotless_operator_method?(method_call)
264
273
  return false if method_call.loc.dot
265
274
 
266
275
  method_call.method?(:[]) || method_call.method?(:[]=) || method_call.operator_method?
@@ -335,8 +344,16 @@ module RuboCop
335
344
 
336
345
  def matching_call_nodes?(left, right)
337
346
  return false unless left && right.respond_to?(:call_type?)
347
+ return false unless left.call_type? && right.call_type?
348
+
349
+ # Compare receiver and method name, but ignore the difference between
350
+ # safe navigation method call (`&.`) and dot method call (`.`).
351
+ left_receiver, left_method, *left_args = left.children
352
+ right_receiver, right_method, *right_args = right.children
338
353
 
339
- left.call_type? && right.call_type? && left.children == right.children
354
+ left_method == right_method &&
355
+ matching_nodes?(left_receiver, right_receiver) &&
356
+ left_args == right_args
340
357
  end
341
358
 
342
359
  def chain_length(method_chain, method)