rubocop 1.66.1 → 1.68.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (123) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  3. data/config/default.yml +55 -6
  4. data/config/internal_affairs.yml +11 -0
  5. data/lib/rubocop/cached_data.rb +12 -4
  6. data/lib/rubocop/cli/command/auto_generate_config.rb +6 -7
  7. data/lib/rubocop/cli/command/execute_runner.rb +1 -1
  8. data/lib/rubocop/cli/command/lsp.rb +2 -2
  9. data/lib/rubocop/cli/command/version.rb +2 -2
  10. data/lib/rubocop/config_loader_resolver.rb +3 -3
  11. data/lib/rubocop/config_validator.rb +2 -1
  12. data/lib/rubocop/cop/autocorrect_logic.rb +22 -2
  13. data/lib/rubocop/cop/base.rb +6 -2
  14. data/lib/rubocop/cop/bundler/gem_version.rb +1 -0
  15. data/lib/rubocop/cop/cop.rb +8 -0
  16. data/lib/rubocop/cop/correctors/alignment_corrector.rb +1 -12
  17. data/lib/rubocop/cop/correctors/parentheses_corrector.rb +1 -1
  18. data/lib/rubocop/cop/correctors/percent_literal_corrector.rb +10 -0
  19. data/lib/rubocop/cop/internal_affairs/cop_description.rb +0 -4
  20. data/lib/rubocop/cop/internal_affairs/redundant_message_argument.rb +6 -21
  21. data/lib/rubocop/cop/internal_affairs/redundant_source_range.rb +8 -1
  22. data/lib/rubocop/cop/internal_affairs/useless_message_assertion.rb +0 -5
  23. data/lib/rubocop/cop/internal_affairs.rb +16 -0
  24. data/lib/rubocop/cop/layout/access_modifier_indentation.rb +5 -1
  25. data/lib/rubocop/cop/layout/def_end_alignment.rb +1 -1
  26. data/lib/rubocop/cop/layout/empty_line_after_guard_clause.rb +1 -1
  27. data/lib/rubocop/cop/layout/first_method_argument_line_break.rb +8 -0
  28. data/lib/rubocop/cop/layout/indentation_width.rb +4 -5
  29. data/lib/rubocop/cop/layout/leading_comment_space.rb +56 -1
  30. data/lib/rubocop/cop/layout/space_before_brackets.rb +5 -5
  31. data/lib/rubocop/cop/layout/space_inside_block_braces.rb +4 -0
  32. data/lib/rubocop/cop/lint/ambiguous_range.rb +4 -1
  33. data/lib/rubocop/cop/lint/big_decimal_new.rb +4 -7
  34. data/lib/rubocop/cop/lint/boolean_symbol.rb +1 -1
  35. data/lib/rubocop/cop/lint/duplicate_branch.rb +39 -4
  36. data/lib/rubocop/cop/lint/duplicate_set_element.rb +74 -0
  37. data/lib/rubocop/cop/lint/ensure_return.rb +0 -3
  38. data/lib/rubocop/cop/lint/erb_new_arguments.rb +1 -1
  39. data/lib/rubocop/cop/lint/float_comparison.rb +1 -1
  40. data/lib/rubocop/cop/lint/implicit_string_concatenation.rb +10 -4
  41. data/lib/rubocop/cop/lint/it_without_arguments_in_block.rb +5 -14
  42. data/lib/rubocop/cop/lint/literal_in_interpolation.rb +25 -2
  43. data/lib/rubocop/cop/lint/non_atomic_file_operation.rb +7 -0
  44. data/lib/rubocop/cop/lint/parentheses_as_grouped_expression.rb +5 -6
  45. data/lib/rubocop/cop/lint/redundant_safe_navigation.rb +1 -1
  46. data/lib/rubocop/cop/lint/safe_navigation_chain.rb +9 -0
  47. data/lib/rubocop/cop/lint/safe_navigation_consistency.rb +107 -41
  48. data/lib/rubocop/cop/lint/symbol_conversion.rb +1 -1
  49. data/lib/rubocop/cop/lint/unescaped_bracket_in_regexp.rb +88 -0
  50. data/lib/rubocop/cop/lint/uri_regexp.rb +25 -7
  51. data/lib/rubocop/cop/metrics/cyclomatic_complexity.rb +4 -1
  52. data/lib/rubocop/cop/mixin/check_line_breakable.rb +10 -0
  53. data/lib/rubocop/cop/mixin/endless_method_rewriter.rb +24 -0
  54. data/lib/rubocop/cop/mixin/frozen_string_literal.rb +3 -1
  55. data/lib/rubocop/cop/mixin/percent_array.rb +1 -1
  56. data/lib/rubocop/cop/mixin/statement_modifier.rb +3 -2
  57. data/lib/rubocop/cop/naming/block_forwarding.rb +1 -1
  58. data/lib/rubocop/cop/naming/inclusive_language.rb +12 -3
  59. data/lib/rubocop/cop/naming/predicate_name.rb +1 -1
  60. data/lib/rubocop/cop/offense.rb +4 -5
  61. data/lib/rubocop/cop/style/access_modifier_declarations.rb +12 -2
  62. data/lib/rubocop/cop/style/accessor_grouping.rb +10 -2
  63. data/lib/rubocop/cop/style/ambiguous_endless_method_definition.rb +79 -0
  64. data/lib/rubocop/cop/style/arguments_forwarding.rb +46 -6
  65. data/lib/rubocop/cop/style/bitwise_predicate.rb +100 -0
  66. data/lib/rubocop/cop/style/block_delimiters.rb +31 -3
  67. data/lib/rubocop/cop/style/collection_compact.rb +10 -10
  68. data/lib/rubocop/cop/style/combinable_defined.rb +115 -0
  69. data/lib/rubocop/cop/style/combinable_loops.rb +7 -0
  70. data/lib/rubocop/cop/style/commented_keyword.rb +7 -1
  71. data/lib/rubocop/cop/style/conditional_assignment.rb +1 -1
  72. data/lib/rubocop/cop/style/data_inheritance.rb +1 -1
  73. data/lib/rubocop/cop/style/endless_method.rb +1 -14
  74. data/lib/rubocop/cop/style/eval_with_location.rb +1 -1
  75. data/lib/rubocop/cop/style/guard_clause.rb +15 -2
  76. data/lib/rubocop/cop/style/hash_each_methods.rb +6 -0
  77. data/lib/rubocop/cop/style/hash_syntax.rb +2 -2
  78. data/lib/rubocop/cop/style/if_inside_else.rb +1 -1
  79. data/lib/rubocop/cop/style/if_with_semicolon.rb +7 -3
  80. data/lib/rubocop/cop/style/keyword_arguments_merging.rb +67 -0
  81. data/lib/rubocop/cop/style/lambda.rb +1 -1
  82. data/lib/rubocop/cop/style/map_into_array.rb +59 -8
  83. data/lib/rubocop/cop/style/method_call_with_args_parentheses/omit_parentheses.rb +12 -7
  84. data/lib/rubocop/cop/style/multiline_memoization.rb +1 -1
  85. data/lib/rubocop/cop/style/multiple_comparison.rb +28 -39
  86. data/lib/rubocop/cop/style/nested_modifier.rb +1 -1
  87. data/lib/rubocop/cop/style/nested_parenthesized_calls.rb +1 -1
  88. data/lib/rubocop/cop/style/one_line_conditional.rb +4 -0
  89. data/lib/rubocop/cop/style/operator_method_call.rb +25 -6
  90. data/lib/rubocop/cop/style/redundant_begin.rb +4 -0
  91. data/lib/rubocop/cop/style/redundant_condition.rb +1 -1
  92. data/lib/rubocop/cop/style/redundant_line_continuation.rb +23 -5
  93. data/lib/rubocop/cop/style/redundant_parentheses.rb +9 -11
  94. data/lib/rubocop/cop/style/require_order.rb +1 -1
  95. data/lib/rubocop/cop/style/rescue_modifier.rb +13 -1
  96. data/lib/rubocop/cop/style/return_nil_in_predicate_method_definition.rb +54 -12
  97. data/lib/rubocop/cop/style/safe_navigation.rb +104 -50
  98. data/lib/rubocop/cop/style/safe_navigation_chain_length.rb +52 -0
  99. data/lib/rubocop/cop/style/select_by_regexp.rb +9 -6
  100. data/lib/rubocop/cop/style/semicolon.rb +1 -1
  101. data/lib/rubocop/cop/style/struct_inheritance.rb +1 -1
  102. data/lib/rubocop/cop/style/ternary_parentheses.rb +26 -5
  103. data/lib/rubocop/cop/style/trivial_accessors.rb +1 -1
  104. data/lib/rubocop/cop/team.rb +8 -1
  105. data/lib/rubocop/cop/util.rb +1 -1
  106. data/lib/rubocop/cop/variable_force/assignment.rb +18 -3
  107. data/lib/rubocop/cop/variable_force/branch.rb +1 -1
  108. data/lib/rubocop/cop/variable_force/variable.rb +5 -1
  109. data/lib/rubocop/cop/variable_force/variable_table.rb +2 -2
  110. data/lib/rubocop/cops_documentation_generator.rb +81 -40
  111. data/lib/rubocop/file_finder.rb +9 -4
  112. data/lib/rubocop/formatter/disabled_config_formatter.rb +1 -1
  113. data/lib/rubocop/lsp/runtime.rb +2 -0
  114. data/lib/rubocop/lsp/server.rb +0 -1
  115. data/lib/rubocop/rspec/expect_offense.rb +1 -0
  116. data/lib/rubocop/runner.rb +17 -6
  117. data/lib/rubocop/server/cache.rb +6 -1
  118. data/lib/rubocop/server/core.rb +1 -0
  119. data/lib/rubocop/target_ruby.rb +13 -13
  120. data/lib/rubocop/version.rb +28 -7
  121. data/lib/rubocop/yaml_duplication_checker.rb +20 -27
  122. data/lib/rubocop.rb +10 -0
  123. metadata +13 -4
@@ -234,11 +234,11 @@ module RuboCop
234
234
  end
235
235
 
236
236
  def autocorrect_heredoc_argument(corrector, node, heredoc_branch, leave_branch, guard)
237
+ remove_whole_lines(corrector, node.loc.end)
237
238
  return unless node.else?
238
239
 
239
240
  remove_whole_lines(corrector, leave_branch.source_range)
240
241
  remove_whole_lines(corrector, node.loc.else)
241
- remove_whole_lines(corrector, node.loc.end)
242
242
  remove_whole_lines(corrector, range_of_branch_to_remove(node, guard))
243
243
  corrector.insert_after(
244
244
  heredoc_branch.last_argument.loc.heredoc_end, "\n#{leave_branch.source}"
@@ -283,7 +283,8 @@ module RuboCop
283
283
  end
284
284
 
285
285
  def accepted_if?(node, ending)
286
- return true if node.modifier_form? || node.ternary? || node.elsif_conditional?
286
+ return true if node.modifier_form? || node.ternary? || node.elsif_conditional? ||
287
+ assigned_lvar_used_in_if_branch?(node)
287
288
 
288
289
  if ending
289
290
  node.else?
@@ -292,6 +293,18 @@ module RuboCop
292
293
  end
293
294
  end
294
295
 
296
+ def assigned_lvar_used_in_if_branch?(node)
297
+ return false unless (if_branch = node.if_branch)
298
+
299
+ assigned_lvars_in_condition = node.condition.each_descendant(:lvasgn).map do |lvasgn|
300
+ lvar_name, = *lvasgn
301
+ lvar_name.to_s
302
+ end
303
+ used_lvars_in_branch = if_branch.each_descendant(:lvar).map(&:source) || []
304
+
305
+ (assigned_lvars_in_condition & used_lvars_in_branch).any?
306
+ end
307
+
295
308
  def remove_whole_lines(corrector, range)
296
309
  corrector.remove(range_by_whole_lines(range, include_final_newline: true))
297
310
  end
@@ -57,6 +57,11 @@ module RuboCop
57
57
  (call $(call _ ${:keys :values}) :each (block_pass (sym _)))
58
58
  PATTERN
59
59
 
60
+ # @!method hash_mutated?(node, receiver)
61
+ def_node_matcher :hash_mutated?, <<~PATTERN
62
+ `(send %1 :[]= ...)
63
+ PATTERN
64
+
60
65
  def on_block(node)
61
66
  return unless handleable?(node)
62
67
 
@@ -103,6 +108,7 @@ module RuboCop
103
108
  def handleable?(node)
104
109
  return false if use_array_converter_method_as_preceding?(node)
105
110
  return false unless (root_receiver = root_receiver(node))
111
+ return false if hash_mutated?(node, root_receiver)
106
112
 
107
113
  !root_receiver.literal? || root_receiver.hash_type?
108
114
  end
@@ -68,7 +68,7 @@ module RuboCop
68
68
  # {a: 1, b: 2}
69
69
  # {:c => 3, 'd' => 4}
70
70
  #
71
- # @example EnforcedShorthandSyntax: always (default)
71
+ # @example EnforcedShorthandSyntax: always
72
72
  #
73
73
  # # bad
74
74
  # {foo: foo, bar: bar}
@@ -84,7 +84,7 @@ module RuboCop
84
84
  # # good
85
85
  # {foo: foo, bar: bar}
86
86
  #
87
- # @example EnforcedShorthandSyntax: either
87
+ # @example EnforcedShorthandSyntax: either (default)
88
88
  #
89
89
  # # good
90
90
  # {foo: foo, bar: bar}
@@ -71,7 +71,7 @@ module RuboCop
71
71
 
72
72
  else_branch = node.else_branch
73
73
 
74
- return unless else_branch&.if_type? && else_branch&.if?
74
+ return unless else_branch&.if_type? && else_branch.if?
75
75
  return if allow_if_modifier_in_else_branch?(else_branch)
76
76
 
77
77
  add_offense(else_branch.loc.keyword) do |corrector|
@@ -81,16 +81,14 @@ module RuboCop
81
81
  RUBY
82
82
  end
83
83
 
84
- # rubocop:disable Metrics/AbcSize
85
84
  def build_expression(expr)
86
- return expr.source if !expr.call_type? || expr.parenthesized? || expr.arguments.empty?
85
+ return expr.source unless require_argument_parentheses?(expr)
87
86
 
88
87
  method = expr.source_range.begin.join(expr.loc.selector.end)
89
88
  arguments = expr.first_argument.source_range.begin.join(expr.source_range.end)
90
89
 
91
90
  "#{method.source}(#{arguments.source})"
92
91
  end
93
- # rubocop:enable Metrics/AbcSize
94
92
 
95
93
  def build_else_branch(second_condition)
96
94
  result = <<~RUBY
@@ -111,6 +109,12 @@ module RuboCop
111
109
 
112
110
  result
113
111
  end
112
+
113
+ def require_argument_parentheses?(node)
114
+ return false unless node.call_type?
115
+
116
+ !node.parenthesized? && node.arguments.any? && !node.method?(:[]) && !node.method?(:[]=)
117
+ end
114
118
  end
115
119
  end
116
120
  end
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Style
6
+ # When passing an existing hash as keyword arguments, provide additional arguments
7
+ # directly rather than using `merge`.
8
+ #
9
+ # Providing arguments directly is more performant, than using `merge`, and
10
+ # also leads to a shorter and simpler code.
11
+ #
12
+ # @example
13
+ # # bad
14
+ # some_method(**opts.merge(foo: true))
15
+ # some_method(**opts.merge(other_opts))
16
+ #
17
+ # # good
18
+ # some_method(**opts, foo: true)
19
+ # some_method(**opts, **other_opts)
20
+ #
21
+ class KeywordArgumentsMerging < Base
22
+ extend AutoCorrector
23
+
24
+ MSG = 'Provide additional arguments directly rather than using `merge`.'
25
+
26
+ # @!method merge_kwargs?(node)
27
+ def_node_matcher :merge_kwargs?, <<~PATTERN
28
+ (send _ _
29
+ ...
30
+ (hash
31
+ (kwsplat
32
+ $(send $_ :merge $...))
33
+ ...))
34
+ PATTERN
35
+
36
+ def on_kwsplat(node)
37
+ return unless (ancestor = node.parent&.parent)
38
+
39
+ merge_kwargs?(ancestor) do |merge_node, hash_node, other_hash_node|
40
+ add_offense(merge_node) do |corrector|
41
+ autocorrect(corrector, node, hash_node, other_hash_node)
42
+ end
43
+ end
44
+ end
45
+
46
+ private
47
+
48
+ def autocorrect(corrector, kwsplat_node, hash_node, other_hash_node)
49
+ other_hash_node_replacement =
50
+ other_hash_node.map do |node|
51
+ if node.hash_type?
52
+ if node.braces?
53
+ node.source[1...-1]
54
+ else
55
+ node.source
56
+ end
57
+ else
58
+ "**#{node.source}"
59
+ end
60
+ end.join(', ')
61
+
62
+ corrector.replace(kwsplat_node, "**#{hash_node.source}, #{other_hash_node_replacement}")
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
@@ -68,7 +68,7 @@ module RuboCop
68
68
 
69
69
  return unless offending_selector?(node, selector)
70
70
 
71
- add_offense(node.send_node.source_range, message: message(node, selector)) do |corrector|
71
+ add_offense(node.send_node, message: message(node, selector)) do |corrector|
72
72
  if node.send_node.lambda_literal?
73
73
  LambdaLiteralToMethodCorrector.new(node).call(corrector)
74
74
  else
@@ -13,8 +13,10 @@ module RuboCop
13
13
  # return value of `Enumerable#map` is an `Array`. They are not autocorrected
14
14
  # when a return value could be used because these types differ.
15
15
  #
16
- # NOTE: It only detects when the mapping destination is a local variable
17
- # initialized as an empty array and referred to only by the pushing operation.
16
+ # NOTE: It only detects when the mapping destination is either:
17
+ # * a local variable initialized as an empty array and referred to only by the
18
+ # pushing operation;
19
+ # * or, if it is the single block argument to a `[].tap` block.
18
20
  # This is because, if not, it's challenging to statically guarantee that the
19
21
  # mapping destination variable remains an empty array:
20
22
  #
@@ -42,6 +44,14 @@ module RuboCop
42
44
  # # good
43
45
  # dest = src.map { |e| e * 2 }
44
46
  #
47
+ # # bad
48
+ # [].tap do |dest|
49
+ # src.each { |e| dest << e * 2 }
50
+ # end
51
+ #
52
+ # # good
53
+ # dest = src.map { |e| e * 2 }
54
+ #
45
55
  # # good - contains another operation
46
56
  # dest = []
47
57
  # src.each { |e| dest << e * 2; puts e }
@@ -53,12 +63,17 @@ module RuboCop
53
63
 
54
64
  MSG = 'Use `%<new_method_name>s` instead of `each` to map elements into an array.'
55
65
 
66
+ # @!method suitable_argument_node?(node)
67
+ def_node_matcher :suitable_argument_node?, <<-PATTERN
68
+ !{splat forwarded-restarg forwarded-args (hash (forwarded-kwrestarg)) (block-pass nil?)}
69
+ PATTERN
70
+
56
71
  # @!method each_block_with_push?(node)
57
72
  def_node_matcher :each_block_with_push?, <<-PATTERN
58
73
  [
59
- ^({begin kwbegin} ...)
74
+ ^({begin kwbegin block} ...)
60
75
  ({block numblock} (send !{nil? self} :each) _
61
- (send (lvar _) {:<< :push :append} {send lvar begin}))
76
+ (send (lvar _) {:<< :push :append} #suitable_argument_node?))
62
77
  ]
63
78
  PATTERN
64
79
 
@@ -74,6 +89,16 @@ module RuboCop
74
89
  )
75
90
  PATTERN
76
91
 
92
+ # @!method empty_array_tap(node)
93
+ def_node_matcher :empty_array_tap, <<~PATTERN
94
+ ^^$(
95
+ block
96
+ (send (array) :tap)
97
+ (args (arg _))
98
+ ...
99
+ )
100
+ PATTERN
101
+
77
102
  # @!method lvar_ref?(node, name)
78
103
  def_node_matcher :lvar_ref?, '(lvar %1)'
79
104
 
@@ -89,9 +114,14 @@ module RuboCop
89
114
  return unless each_block_with_push?(node)
90
115
 
91
116
  dest_var = find_dest_var(node)
92
- return unless (asgn = find_closest_assignment(node, dest_var))
93
- return unless empty_array_asgn?(asgn)
94
- return unless dest_used_only_for_mapping?(node, dest_var, asgn)
117
+
118
+ if offending_empty_array_tap?(node, dest_var)
119
+ asgn = dest_var.declaration_node
120
+ else
121
+ return unless (asgn = find_closest_assignment(node, dest_var))
122
+ return unless empty_array_asgn?(asgn)
123
+ return unless dest_used_only_for_mapping?(node, dest_var, asgn)
124
+ end
95
125
 
96
126
  register_offense(node, dest_var, asgn)
97
127
  end
@@ -108,6 +138,15 @@ module RuboCop
108
138
  candidates.find { |v| v.references.any? { |n| n.node.equal?(node) } }
109
139
  end
110
140
 
141
+ def offending_empty_array_tap?(node, dest_var)
142
+ return false unless (tap_block_node = empty_array_tap(dest_var.declaration_node))
143
+
144
+ # A `tap` block only offends if the array push is the only thing in it;
145
+ # otherwise we cannot guarantee that the block variable is still an empty
146
+ # array when pushed to.
147
+ tap_block_node.body == node
148
+ end
149
+
111
150
  def find_closest_assignment(block, dest_var)
112
151
  dest_var.assignments.reverse_each.lazy.map(&:node).find do |node|
113
152
  node.source_range.end_pos < block.source_range.begin_pos
@@ -127,7 +166,13 @@ module RuboCop
127
166
  next if return_value_used?(block)
128
167
 
129
168
  corrector.replace(block.send_node.selector, new_method_name)
130
- remove_assignment(corrector, asgn)
169
+
170
+ if (tap_block_node = empty_array_tap(dest_var.declaration_node))
171
+ remove_tap(corrector, block, tap_block_node)
172
+ else
173
+ remove_assignment(corrector, asgn)
174
+ end
175
+
131
176
  correct_push_node(corrector, block.body)
132
177
  correct_return_value_handling(corrector, block, dest_var)
133
178
  end
@@ -159,6 +204,12 @@ module RuboCop
159
204
  corrector.remove(range)
160
205
  end
161
206
 
207
+ def remove_tap(corrector, node, block_node)
208
+ range = range_between(block_node.source_range.begin_pos, node.source_range.begin_pos)
209
+ corrector.remove(range)
210
+ corrector.remove(range_with_surrounding_space(block_node.loc.end, side: :left))
211
+ end
212
+
162
213
  def correct_push_node(corrector, push_node)
163
214
  range = push_node.source_range
164
215
  arg_range = push_node.first_argument.source_range
@@ -7,6 +7,8 @@ module RuboCop
7
7
  # Style omit_parentheses
8
8
  # rubocop:disable Metrics/ModuleLength, Metrics/CyclomaticComplexity
9
9
  module OmitParentheses
10
+ include RangeHelp
11
+
10
12
  TRAILING_WHITESPACE_REGEX = /\s+\Z/.freeze
11
13
  OMIT_MSG = 'Omit parentheses for method calls with arguments.'
12
14
  private_constant :OMIT_MSG
@@ -30,10 +32,13 @@ module RuboCop
30
32
  end
31
33
 
32
34
  def autocorrect(corrector, node)
35
+ range = args_begin(node)
33
36
  if parentheses_at_the_end_of_multiline_call?(node)
34
- corrector.replace(args_begin(node), ' \\')
37
+ # Whitespace after line continuation (`\ `) is a syntax error
38
+ with_whitespace = range_with_surrounding_space(range, side: :right, newlines: false)
39
+ corrector.replace(with_whitespace, ' \\')
35
40
  else
36
- corrector.replace(args_begin(node), ' ')
41
+ corrector.replace(range, ' ')
37
42
  end
38
43
  corrector.remove(node.loc.end)
39
44
  end
@@ -47,11 +52,11 @@ module RuboCop
47
52
  node.each_ancestor(:def, :defs).any?(&:endless?) && node.arguments.any?
48
53
  end
49
54
 
50
- def require_parentheses_for_hash_value_omission?(node)
55
+ def require_parentheses_for_hash_value_omission?(node) # rubocop:disable Metrics/PerceivedComplexity
51
56
  return false unless (last_argument = node.last_argument)
52
57
  return false if !last_argument.hash_type? || !last_argument.pairs.last&.value_omission?
53
58
 
54
- node.parent&.conditional? || !last_expression?(node)
59
+ node.parent&.conditional? || node.parent&.single_line? || !last_expression?(node)
55
60
  end
56
61
 
57
62
  # Require hash value omission be enclosed in parentheses to prevent the following issue:
@@ -127,7 +132,7 @@ module RuboCop
127
132
  end
128
133
 
129
134
  def call_in_single_line_inheritance?(node)
130
- node.parent&.class_type? && node.parent&.single_line?
135
+ node.parent&.class_type? && node.parent.single_line?
131
136
  end
132
137
 
133
138
  def call_with_ambiguous_arguments?(node) # rubocop:disable Metrics/PerceivedComplexity
@@ -147,7 +152,7 @@ module RuboCop
147
152
  end
148
153
 
149
154
  def call_in_argument_with_block?(node)
150
- parent = node.parent&.block_type? && node.parent&.parent
155
+ parent = node.parent&.block_type? && node.parent.parent
151
156
  return false unless parent
152
157
 
153
158
  parent.call_type? || parent.super_type? || parent.yield_type?
@@ -211,7 +216,7 @@ module RuboCop
211
216
 
212
217
  def unary_literal?(node)
213
218
  (node.numeric_type? && node.sign?) ||
214
- (node.parent&.send_type? && node.parent&.unary_operation?)
219
+ (node.parent&.send_type? && node.parent.unary_operation?)
215
220
  end
216
221
 
217
222
  def assigned_before?(node, target)
@@ -43,7 +43,7 @@ module RuboCop
43
43
 
44
44
  return unless bad_rhs?(rhs)
45
45
 
46
- add_offense(node.source_range) do |corrector|
46
+ add_offense(node) do |corrector|
47
47
  if style == :keyword
48
48
  keyword_autocorrect(rhs, corrector)
49
49
  else
@@ -55,25 +55,19 @@ module RuboCop
55
55
  MSG = 'Avoid comparing a variable with multiple items ' \
56
56
  'in a conditional, use `Array#include?` instead.'
57
57
 
58
- def on_new_investigation
59
- reset_comparison
60
- end
61
-
62
58
  def on_or(node)
63
59
  root_of_or_node = root_of_or_node(node)
64
-
65
60
  return unless node == root_of_or_node
66
- return unless nested_variable_comparison?(root_of_or_node)
67
- return if @allowed_method_comparison
68
- return if @compared_elements.size < comparisons_threshold
61
+ return unless nested_comparison?(node)
62
+
63
+ return unless (variable, values = find_offending_var(node))
64
+ return if values.size < comparisons_threshold
69
65
 
70
66
  add_offense(node) do |corrector|
71
- elements = @compared_elements.join(', ')
72
- prefer_method = "[#{elements}].include?(#{variables_in_node(node).first})"
67
+ elements = values.map(&:source).join(', ')
68
+ prefer_method = "[#{elements}].include?(#{variable_name(variable)})"
73
69
 
74
70
  corrector.replace(node, prefer_method)
75
-
76
- reset_comparison
77
71
  end
78
72
  end
79
73
 
@@ -92,32 +86,25 @@ module RuboCop
92
86
  (send $_ :== $lvar)
93
87
  PATTERN
94
88
 
95
- def nested_variable_comparison?(node)
96
- return false unless nested_comparison?(node)
97
-
98
- variables_in_node(node).count == 1
99
- end
100
-
101
- def variables_in_node(node)
89
+ # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
90
+ def find_offending_var(node, variables = Set.new, values = [])
102
91
  if node.or_type?
103
- node.node_parts.flat_map { |node_part| variables_in_node(node_part) }.uniq
104
- else
105
- variables_in_simple_node(node)
106
- end
107
- end
92
+ find_offending_var(node.lhs, variables, values)
93
+ find_offending_var(node.rhs, variables, values)
94
+ elsif simple_double_comparison?(node)
95
+ return
96
+ elsif (var, obj = simple_comparison?(node))
97
+ return if allow_method_comparison? && obj.send_type?
108
98
 
109
- def variables_in_simple_node(node)
110
- simple_double_comparison?(node) do |var1, var2|
111
- return [variable_name(var1), variable_name(var2)]
112
- end
113
- if (var, obj = simple_comparison_lhs?(node)) || (obj, var = simple_comparison_rhs?(node))
114
- @allowed_method_comparison = true if allow_method_comparison? && obj.send_type?
115
- @compared_elements << obj.source
116
- return [variable_name(var)]
99
+ variables << var
100
+ return if variables.size > 1
101
+
102
+ values << obj
117
103
  end
118
104
 
119
- []
105
+ [variables.first, values] if variables.any?
120
106
  end
107
+ # rubocop:enable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
121
108
 
122
109
  def variable_name(node)
123
110
  node.children[0]
@@ -132,7 +119,14 @@ module RuboCop
132
119
  end
133
120
 
134
121
  def comparison?(node)
135
- simple_comparison_lhs?(node) || simple_comparison_rhs?(node) || nested_comparison?(node)
122
+ simple_comparison?(node) || nested_comparison?(node)
123
+ end
124
+
125
+ def simple_comparison?(node)
126
+ if (var, obj = simple_comparison_lhs?(node)) ||
127
+ (obj, var = simple_comparison_rhs?(node))
128
+ [var, obj]
129
+ end
136
130
  end
137
131
 
138
132
  def root_of_or_node(or_node)
@@ -145,11 +139,6 @@ module RuboCop
145
139
  end
146
140
  end
147
141
 
148
- def reset_comparison
149
- @compared_elements = []
150
- @allowed_method_comparison = false
151
- end
152
-
153
142
  def allow_method_comparison?
154
143
  cop_config.fetch('AllowMethodComparison', true)
155
144
  end
@@ -36,7 +36,7 @@ module RuboCop
36
36
  end
37
37
 
38
38
  def modifier?(node)
39
- node&.basic_conditional? && node&.modifier_form?
39
+ node&.basic_conditional? && node.modifier_form?
40
40
  end
41
41
 
42
42
  def autocorrect(corrector, node)
@@ -39,7 +39,7 @@ module RuboCop
39
39
  next if allowed_omission?(nested)
40
40
 
41
41
  message = format(MSG, source: nested.source)
42
- add_offense(nested.source_range, message: message) do |corrector|
42
+ add_offense(nested, message: message) do |corrector|
43
43
  autocorrect(corrector, nested)
44
44
  end
45
45
  end
@@ -46,7 +46,11 @@ module RuboCop
46
46
 
47
47
  message = message(node)
48
48
  add_offense(node, message: message) do |corrector|
49
+ next if part_of_ignored_node?(node)
50
+
49
51
  autocorrect(corrector, node)
52
+
53
+ ignore_node(node)
50
54
  end
51
55
  end
52
56
 
@@ -22,6 +22,9 @@ module RuboCop
22
22
 
23
23
  MSG = 'Redundant dot detected.'
24
24
  RESTRICT_ON_SEND = %i[| ^ & <=> == === =~ > >= < <= << >> + - * / % ** ~ ! != !~].freeze
25
+ INVALID_SYNTAX_ARG_TYPES = %i[
26
+ splat kwsplat forwarded_args forwarded_restarg forwarded_kwrestarg block_pass
27
+ ].freeze
25
28
 
26
29
  # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity
27
30
  def on_send(node)
@@ -29,14 +32,16 @@ module RuboCop
29
32
  return if node.receiver.const_type? || !node.arguments.one?
30
33
 
31
34
  _lhs, _op, rhs = *node
32
- return if !rhs || method_call_with_parenthesized_arg?(rhs) || anonymous_forwarding?(rhs)
35
+ if !rhs || method_call_with_parenthesized_arg?(rhs) || invalid_syntax_argument?(rhs)
36
+ return
37
+ end
33
38
 
34
39
  add_offense(dot) do |corrector|
35
40
  wrap_in_parentheses_if_chained(corrector, node)
36
41
  corrector.replace(dot, ' ')
37
42
 
38
43
  selector = node.loc.selector
39
- corrector.insert_after(selector, ' ') if selector.end_pos == rhs.source_range.begin_pos
44
+ corrector.insert_after(selector, ' ') if insert_space_after?(node)
40
45
  end
41
46
  end
42
47
  # rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity
@@ -50,11 +55,10 @@ module RuboCop
50
55
  argument.children.first && argument.parent.parenthesized?
51
56
  end
52
57
 
53
- def anonymous_forwarding?(argument)
54
- return true if argument.forwarded_args_type? || argument.forwarded_restarg_type?
55
- return true if argument.hash_type? && argument.children.first&.forwarded_kwrestarg_type?
58
+ def invalid_syntax_argument?(argument)
59
+ type = argument.hash_type? ? argument.children.first&.type : argument.type
56
60
 
57
- argument.block_pass_type? && argument.source == '&'
61
+ INVALID_SYNTAX_ARG_TYPES.include?(type)
58
62
  end
59
63
 
60
64
  def wrap_in_parentheses_if_chained(corrector, node)
@@ -67,6 +71,21 @@ module RuboCop
67
71
  corrector.insert_after(operator, ' ')
68
72
  corrector.wrap(node, '(', ')')
69
73
  end
74
+
75
+ def insert_space_after?(node)
76
+ _lhs, op, rhs = *node
77
+ selector = node.loc.selector
78
+
79
+ return true if selector.end_pos == rhs.source_range.begin_pos
80
+ return false if node.parent&.call_type? # if chained, a space is already added
81
+
82
+ # For `/` operations, if the RHS starts with a `(` without space,
83
+ # add one to avoid a syntax error.
84
+ range = selector.end.join(rhs.source_range.begin)
85
+ return true if op == :/ && range.source == '('
86
+
87
+ false
88
+ end
70
89
  end
71
90
  end
72
91
  end
@@ -68,6 +68,10 @@ module RuboCop
68
68
 
69
69
  MSG = 'Redundant `begin` block detected.'
70
70
 
71
+ def self.autocorrect_incompatible_with
72
+ [Style::BlockDelimiters]
73
+ end
74
+
71
75
  # @!method offensive_kwbegins(node)
72
76
  def_node_search :offensive_kwbegins, <<~PATTERN
73
77
  [(kwbegin ...) !#allowable_kwbegin?]
@@ -94,7 +94,7 @@ module RuboCop
94
94
  end
95
95
 
96
96
  def use_hash_key_assignment?(else_branch)
97
- else_branch&.send_type? && else_branch&.method?(:[]=)
97
+ else_branch&.send_type? && else_branch.method?(:[]=)
98
98
  end
99
99
 
100
100
  def use_hash_key_access?(node)