rubocop 1.66.1 → 1.68.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 (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)