rubocop 1.53.1 → 1.57.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (132) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +3 -1
  3. data/config/default.yml +34 -8
  4. data/config/obsoletion.yml +5 -0
  5. data/lib/rubocop/cli/command/auto_generate_config.rb +10 -5
  6. data/lib/rubocop/cli.rb +1 -1
  7. data/lib/rubocop/config_finder.rb +2 -2
  8. data/lib/rubocop/config_obsoletion/parameter_rule.rb +9 -1
  9. data/lib/rubocop/cop/autocorrect_logic.rb +3 -1
  10. data/lib/rubocop/cop/base.rb +1 -1
  11. data/lib/rubocop/cop/bundler/duplicated_gem.rb +1 -0
  12. data/lib/rubocop/cop/bundler/duplicated_group.rb +127 -0
  13. data/lib/rubocop/cop/bundler/ordered_gems.rb +9 -1
  14. data/lib/rubocop/cop/correctors/lambda_literal_to_method_corrector.rb +7 -4
  15. data/lib/rubocop/cop/gemspec/ordered_dependencies.rb +9 -1
  16. data/lib/rubocop/cop/generator/require_file_injector.rb +1 -1
  17. data/lib/rubocop/cop/internal_affairs/example_description.rb +42 -21
  18. data/lib/rubocop/cop/internal_affairs/location_line_equality_comparison.rb +3 -1
  19. data/lib/rubocop/cop/internal_affairs/redundant_method_dispatch_node.rb +11 -2
  20. data/lib/rubocop/cop/internal_affairs/useless_message_assertion.rb +2 -0
  21. data/lib/rubocop/cop/layout/dot_position.rb +1 -5
  22. data/lib/rubocop/cop/layout/empty_line_after_guard_clause.rb +42 -9
  23. data/lib/rubocop/cop/layout/empty_line_between_defs.rb +26 -3
  24. data/lib/rubocop/cop/layout/end_alignment.rb +7 -1
  25. data/lib/rubocop/cop/layout/heredoc_indentation.rb +3 -0
  26. data/lib/rubocop/cop/layout/indentation_width.rb +1 -1
  27. data/lib/rubocop/cop/layout/leading_comment_space.rb +1 -1
  28. data/lib/rubocop/cop/layout/line_continuation_leading_space.rb +17 -9
  29. data/lib/rubocop/cop/layout/line_continuation_spacing.rb +1 -1
  30. data/lib/rubocop/cop/layout/line_end_string_concatenation_indentation.rb +2 -0
  31. data/lib/rubocop/cop/layout/multiline_method_call_indentation.rb +18 -3
  32. data/lib/rubocop/cop/layout/redundant_line_break.rb +13 -3
  33. data/lib/rubocop/cop/layout/space_after_comma.rb +9 -1
  34. data/lib/rubocop/cop/layout/space_after_not.rb +1 -1
  35. data/lib/rubocop/cop/layout/space_around_method_call_operator.rb +2 -2
  36. data/lib/rubocop/cop/layout/space_around_operators.rb +3 -1
  37. data/lib/rubocop/cop/layout/space_inside_parens.rb +1 -1
  38. data/lib/rubocop/cop/layout/trailing_empty_lines.rb +5 -0
  39. data/lib/rubocop/cop/lint/debugger.rb +17 -4
  40. data/lib/rubocop/cop/lint/empty_block.rb +1 -1
  41. data/lib/rubocop/cop/lint/literal_in_interpolation.rb +1 -1
  42. data/lib/rubocop/cop/lint/mixed_case_range.rb +3 -1
  43. data/lib/rubocop/cop/lint/non_atomic_file_operation.rb +10 -7
  44. data/lib/rubocop/cop/lint/redundant_regexp_quantifiers.rb +10 -0
  45. data/lib/rubocop/cop/lint/redundant_require_statement.rb +4 -0
  46. data/lib/rubocop/cop/lint/redundant_safe_navigation.rb +20 -4
  47. data/lib/rubocop/cop/lint/safe_navigation_chain.rb +11 -4
  48. data/lib/rubocop/cop/lint/shadowing_outer_local_variable.rb +7 -1
  49. data/lib/rubocop/cop/lint/struct_new_override.rb +12 -12
  50. data/lib/rubocop/cop/lint/suppressed_exception.rb +1 -1
  51. data/lib/rubocop/cop/lint/to_enum_arguments.rb +5 -3
  52. data/lib/rubocop/cop/lint/useless_assignment.rb +38 -12
  53. data/lib/rubocop/cop/lint/void.rb +32 -20
  54. data/lib/rubocop/cop/metrics/block_length.rb +1 -1
  55. data/lib/rubocop/cop/metrics/class_length.rb +8 -3
  56. data/lib/rubocop/cop/metrics/method_length.rb +1 -1
  57. data/lib/rubocop/cop/metrics/utils/code_length_calculator.rb +32 -4
  58. data/lib/rubocop/cop/mixin/comments_help.rb +16 -12
  59. data/lib/rubocop/cop/mixin/def_node.rb +1 -1
  60. data/lib/rubocop/cop/mixin/hash_shorthand_syntax.rb +14 -11
  61. data/lib/rubocop/cop/mixin/heredoc.rb +6 -2
  62. data/lib/rubocop/cop/mixin/multiline_expression_indentation.rb +3 -2
  63. data/lib/rubocop/cop/mixin/preceding_following_alignment.rb +5 -7
  64. data/lib/rubocop/cop/mixin/string_help.rb +4 -2
  65. data/lib/rubocop/cop/mixin/trailing_comma.rb +1 -1
  66. data/lib/rubocop/cop/naming/file_name.rb +1 -1
  67. data/lib/rubocop/cop/naming/heredoc_delimiter_naming.rb +3 -1
  68. data/lib/rubocop/cop/style/alias.rb +9 -8
  69. data/lib/rubocop/cop/style/arguments_forwarding.rb +280 -63
  70. data/lib/rubocop/cop/style/array_intersect.rb +13 -5
  71. data/lib/rubocop/cop/style/block_delimiters.rb +2 -1
  72. data/lib/rubocop/cop/style/class_equality_comparison.rb +7 -0
  73. data/lib/rubocop/cop/style/collection_methods.rb +2 -0
  74. data/lib/rubocop/cop/style/combinable_loops.rb +4 -2
  75. data/lib/rubocop/cop/style/concat_array_literals.rb +1 -1
  76. data/lib/rubocop/cop/style/empty_case_condition.rb +6 -1
  77. data/lib/rubocop/cop/style/for.rb +1 -1
  78. data/lib/rubocop/cop/style/format_string.rb +24 -3
  79. data/lib/rubocop/cop/style/frozen_string_literal_comment.rb +3 -1
  80. data/lib/rubocop/cop/style/guard_clause.rb +26 -0
  81. data/lib/rubocop/cop/style/hash_conversion.rb +10 -0
  82. data/lib/rubocop/cop/style/identical_conditional_branches.rb +25 -3
  83. data/lib/rubocop/cop/style/if_with_semicolon.rb +2 -2
  84. data/lib/rubocop/cop/style/lambda.rb +3 -3
  85. data/lib/rubocop/cop/style/lambda_call.rb +5 -0
  86. data/lib/rubocop/cop/style/method_call_with_args_parentheses/omit_parentheses.rb +8 -1
  87. data/lib/rubocop/cop/style/mixin_grouping.rb +1 -1
  88. data/lib/rubocop/cop/style/multiline_block_chain.rb +1 -1
  89. data/lib/rubocop/cop/style/multiline_ternary_operator.rb +1 -1
  90. data/lib/rubocop/cop/style/nested_ternary_operator.rb +3 -11
  91. data/lib/rubocop/cop/style/open_struct_use.rb +1 -1
  92. data/lib/rubocop/cop/style/operator_method_call.rb +6 -0
  93. data/lib/rubocop/cop/style/redundant_argument.rb +6 -1
  94. data/lib/rubocop/cop/style/redundant_begin.rb +9 -1
  95. data/lib/rubocop/cop/style/redundant_conditional.rb +1 -9
  96. data/lib/rubocop/cop/style/redundant_double_splat_hash_braces.rb +93 -5
  97. data/lib/rubocop/cop/style/redundant_exception.rb +32 -12
  98. data/lib/rubocop/cop/style/redundant_filter_chain.rb +22 -5
  99. data/lib/rubocop/cop/style/redundant_parentheses.rb +41 -15
  100. data/lib/rubocop/cop/style/redundant_regexp_argument.rb +4 -1
  101. data/lib/rubocop/cop/style/redundant_return.rb +7 -2
  102. data/lib/rubocop/cop/style/redundant_self_assignment_branch.rb +5 -0
  103. data/lib/rubocop/cop/style/return_nil.rb +6 -2
  104. data/lib/rubocop/cop/style/return_nil_in_predicate_method_definition.rb +23 -9
  105. data/lib/rubocop/cop/style/semicolon.rb +0 -3
  106. data/lib/rubocop/cop/style/single_argument_dig.rb +2 -1
  107. data/lib/rubocop/cop/style/single_line_do_end_block.rb +67 -0
  108. data/lib/rubocop/cop/style/sole_nested_conditional.rb +3 -1
  109. data/lib/rubocop/cop/style/string_literals_in_interpolation.rb +30 -5
  110. data/lib/rubocop/cop/style/symbol_array.rb +35 -15
  111. data/lib/rubocop/cop/style/yoda_condition.rb +4 -2
  112. data/lib/rubocop/cop/style/yoda_expression.rb +8 -7
  113. data/lib/rubocop/cop/utils/regexp_ranges.rb +26 -13
  114. data/lib/rubocop/cop/variable_force/assignment.rb +14 -5
  115. data/lib/rubocop/file_finder.rb +4 -7
  116. data/lib/rubocop/formatter/html_formatter.rb +4 -2
  117. data/lib/rubocop/formatter/junit_formatter.rb +1 -1
  118. data/lib/rubocop/lsp/routes.rb +41 -18
  119. data/lib/rubocop/lsp/runtime.rb +22 -2
  120. data/lib/rubocop/lsp/server.rb +10 -4
  121. data/lib/rubocop/magic_comment.rb +12 -10
  122. data/lib/rubocop/result_cache.rb +4 -0
  123. data/lib/rubocop/rspec/shared_contexts.rb +2 -3
  124. data/lib/rubocop/runner.rb +5 -3
  125. data/lib/rubocop/server/cache.rb +1 -0
  126. data/lib/rubocop/server/client_command/exec.rb +1 -1
  127. data/lib/rubocop/string_interpreter.rb +3 -3
  128. data/lib/rubocop/target_finder.rb +7 -3
  129. data/lib/rubocop/target_ruby.rb +9 -5
  130. data/lib/rubocop/version.rb +1 -1
  131. data/lib/rubocop.rb +2 -0
  132. metadata +16 -14
@@ -75,9 +75,7 @@ module RuboCop
75
75
  end
76
76
 
77
77
  def aligned_token?(range, line)
78
- aligned_words?(range, line) ||
79
- aligned_char?(range, line) ||
80
- aligned_assignment?(range, line)
78
+ aligned_words?(range, line) || aligned_assignment?(range, line)
81
79
  end
82
80
 
83
81
  def aligned_operator?(range, line)
@@ -85,11 +83,11 @@ module RuboCop
85
83
  end
86
84
 
87
85
  def aligned_words?(range, line)
88
- /\s\S/.match?(line[range.column - 1, 2])
89
- end
86
+ left_edge = range.column
87
+ return true if /\s\S/.match?(line[left_edge - 1, 2])
90
88
 
91
- def aligned_char?(range, line)
92
- line[range.column] == range.source[0]
89
+ token = range.source
90
+ token == line[left_edge, token.length]
93
91
  end
94
92
 
95
93
  def aligned_assignment?(range, line)
@@ -30,8 +30,10 @@ module RuboCop
30
30
  private
31
31
 
32
32
  def inside_interpolation?(node)
33
- # A :begin node inside a :dstr node is an interpolation.
34
- node.ancestors.drop_while { |a| !a.begin_type? }.any?(&:dstr_type?)
33
+ # A :begin node inside a :dstr, :dsym, or :regexp node is an interpolation.
34
+ node.ancestors
35
+ .drop_while { |a| !a.begin_type? }
36
+ .any? { |a| a.dstr_type? || a.dsym_type? || a.regexp_type? }
35
37
  end
36
38
  end
37
39
  end
@@ -106,7 +106,7 @@ module RuboCop
106
106
  end
107
107
 
108
108
  def elements(node)
109
- return node.children unless %i[csend send].include?(node.type)
109
+ return node.children unless node.call_type?
110
110
 
111
111
  node.arguments.flat_map do |argument|
112
112
  # For each argument, if it is a multi-line hash without braces,
@@ -136,7 +136,7 @@ module RuboCop
136
136
  end
137
137
 
138
138
  def filename_good?(basename)
139
- basename = basename.sub(/^\./, '')
139
+ basename = basename.delete_prefix('.')
140
140
  basename = basename.sub(/\.[^.]+$/, '')
141
141
  # special handling for Action Pack Variants file names like
142
142
  # some_file.xlsx+mobile.axlsx
@@ -31,7 +31,9 @@ module RuboCop
31
31
  def on_heredoc(node)
32
32
  return if meaningful_delimiters?(node)
33
33
 
34
- add_offense(node.loc.heredoc_end)
34
+ range = node.children.empty? ? node : node.loc.heredoc_end
35
+
36
+ add_offense(range)
35
37
  end
36
38
 
37
39
  private
@@ -122,7 +122,7 @@ module RuboCop
122
122
  end
123
123
 
124
124
  def bareword?(sym_node)
125
- !sym_node.source.start_with?(':')
125
+ !sym_node.source.start_with?(':') || sym_node.dsym_type?
126
126
  end
127
127
 
128
128
  def correct_alias_method_to_alias(corrector, send_node)
@@ -134,9 +134,7 @@ module RuboCop
134
134
 
135
135
  def correct_alias_to_alias_method(corrector, node)
136
136
  replacement =
137
- 'alias_method ' \
138
- ":#{identifier(node.new_identifier)}, " \
139
- ":#{identifier(node.old_identifier)}"
137
+ "alias_method #{identifier(node.new_identifier)}, #{identifier(node.old_identifier)}"
140
138
 
141
139
  corrector.replace(node, replacement)
142
140
  end
@@ -146,10 +144,13 @@ module RuboCop
146
144
  corrector.replace(node.old_identifier, node.old_identifier.source[1..])
147
145
  end
148
146
 
149
- # @!method identifier(node)
150
- def_node_matcher :identifier, <<~PATTERN
151
- (sym $_)
152
- PATTERN
147
+ def identifier(node)
148
+ if node.sym_type?
149
+ ":#{node.children.first}"
150
+ else
151
+ node.source
152
+ end
153
+ end
153
154
  end
154
155
  end
155
156
  end
@@ -8,6 +8,12 @@ module RuboCop
8
8
  # This cop identifies places where `do_something(*args, &block)`
9
9
  # can be replaced by `do_something(...)`.
10
10
  #
11
+ # In Ruby 3.2, anonymous args/kwargs forwarding has been added.
12
+ #
13
+ # This cop also identifies places where `use_args(*args)`/`use_kwargs(**kwargs)` can be
14
+ # replaced by `use_args(*)`/`use_kwargs(**)`; if desired, this functionality can be disabled
15
+ # by setting UseAnonymousForwarding: false.
16
+ #
11
17
  # @example
12
18
  # # bad
13
19
  # def foo(*args, &block)
@@ -24,7 +30,27 @@ module RuboCop
24
30
  # bar(...)
25
31
  # end
26
32
  #
27
- # @example AllowOnlyRestArgument: true (default)
33
+ # @example UseAnonymousForwarding: true (default, only relevant for Ruby >= 3.2)
34
+ # # bad
35
+ # def foo(*args, **kwargs)
36
+ # args_only(*args)
37
+ # kwargs_only(**kwargs)
38
+ # end
39
+ #
40
+ # # good
41
+ # def foo(*, **)
42
+ # args_only(*)
43
+ # kwargs_only(**)
44
+ # end
45
+ #
46
+ # @example UseAnonymousForwarding: false (only relevant for Ruby >= 3.2)
47
+ # # good
48
+ # def foo(*args, **kwargs)
49
+ # args_only(*args)
50
+ # kwargs_only(**kwargs)
51
+ # end
52
+ #
53
+ # @example AllowOnlyRestArgument: true (default, only relevant for Ruby < 3.2)
28
54
  # # good
29
55
  # def foo(*args)
30
56
  # bar(*args)
@@ -34,7 +60,7 @@ module RuboCop
34
60
  # bar(**kwargs)
35
61
  # end
36
62
  #
37
- # @example AllowOnlyRestArgument: false
63
+ # @example AllowOnlyRestArgument: false (only relevant for Ruby < 3.2)
38
64
  # # bad
39
65
  # # The following code can replace the arguments with `...`,
40
66
  # # but it will change the behavior. Because `...` forwards block also.
@@ -53,102 +79,293 @@ module RuboCop
53
79
 
54
80
  minimum_target_ruby_version 2.7
55
81
 
56
- MSG = 'Use arguments forwarding.'
57
-
58
- # @!method use_rest_arguments?(node)
59
- def_node_matcher :use_rest_arguments?, <<~PATTERN
60
- (args ({restarg kwrestarg} $_) $...)
61
- PATTERN
62
-
63
- # @!method only_rest_arguments?(node, name)
64
- def_node_matcher :only_rest_arguments?, <<~PATTERN
65
- {
66
- (send _ _ (splat (lvar %1)))
67
- (send _ _ (hash (kwsplat (lvar %1))))
68
- }
69
- PATTERN
70
-
71
- # @!method forwarding_method_arguments?(node, rest_name, block_name, kwargs_name)
72
- def_node_matcher :forwarding_method_arguments?, <<~PATTERN
73
- {
74
- (send _ _
75
- (splat (lvar %1))
76
- (block-pass {(lvar %2) nil?}))
77
- (send _ _
78
- (splat (lvar %1))
79
- (hash (kwsplat (lvar %3)))
80
- (block-pass {(lvar %2) nil?}))
81
- }
82
- PATTERN
82
+ FORWARDING_LVAR_TYPES = %i[splat kwsplat block_pass].freeze
83
+ ADDITIONAL_ARG_TYPES = %i[lvar arg].freeze
84
+
85
+ FORWARDING_MSG = 'Use shorthand syntax `...` for arguments forwarding.'
86
+ ARGS_MSG = 'Use anonymous positional arguments forwarding (`*`).'
87
+ KWARGS_MSG = 'Use anonymous keyword arguments forwarding (`**`).'
83
88
 
84
89
  def on_def(node)
85
90
  return unless node.body
86
- return unless (rest_args_name, args = use_rest_arguments?(node.arguments))
87
- return if args.any?(&:default?)
88
91
 
89
- node.each_descendant(:send) do |send_node|
90
- kwargs_name, block_name = extract_argument_names_from(args)
92
+ forwardable_args = extract_forwardable_args(node.arguments)
93
+
94
+ send_classifications = classify_send_nodes(
95
+ node,
96
+ node.each_descendant(:send).to_a,
97
+ non_splat_or_block_pass_lvar_references(node.body),
98
+ forwardable_args
99
+ )
91
100
 
92
- next unless forwarding_method?(send_node, rest_args_name, kwargs_name, block_name) &&
93
- all_lvars_as_forwarding_method_arguments?(node, send_node)
101
+ return if send_classifications.empty?
94
102
 
95
- register_offense_to_forwarding_method_arguments(send_node)
96
- register_offense_to_method_definition_arguments(node)
103
+ if only_forwards_all?(send_classifications)
104
+ add_forward_all_offenses(node, send_classifications, forwardable_args)
105
+ elsif target_ruby_version >= 3.2
106
+ add_post_ruby_32_offenses(node, send_classifications, forwardable_args)
97
107
  end
98
108
  end
109
+
99
110
  alias on_defs on_def
100
111
 
101
112
  private
102
113
 
103
- def extract_argument_names_from(args)
104
- kwargs_name = args.first.source.delete('**') if args.first&.kwrestarg_type?
105
- block_arg_name = args.last.source.delete('&') if args.last&.blockarg_type?
114
+ def extract_forwardable_args(args)
115
+ [args.find(&:restarg_type?), args.find(&:kwrestarg_type?), args.find(&:blockarg_type?)]
116
+ end
106
117
 
107
- [kwargs_name, block_arg_name].map { |name| name&.to_sym }
118
+ def only_forwards_all?(send_classifications)
119
+ send_classifications.all? { |_, c, _, _| c == :all }
108
120
  end
109
121
 
110
- def forwarding_method?(node, rest_arg, kwargs, block_arg)
111
- return only_rest_arguments?(node, rest_arg) unless allow_only_rest_arguments?
122
+ def add_forward_all_offenses(node, send_classifications, forwardable_args)
123
+ send_classifications.each do |send_node, _c, forward_rest, _forward_kwrest|
124
+ register_forward_all_offense(send_node, send_node, forward_rest)
125
+ end
112
126
 
113
- forwarding_method_arguments?(node, rest_arg, block_arg, kwargs)
127
+ rest_arg, _kwrest_arg, _block_arg = *forwardable_args
128
+ register_forward_all_offense(node, node.arguments, rest_arg)
114
129
  end
115
130
 
116
- def all_lvars_as_forwarding_method_arguments?(def_node, forwarding_method)
117
- lvars = def_node.body.each_descendant(:lvar, :lvasgn)
131
+ def add_post_ruby_32_offenses(def_node, send_classifications, forwardable_args)
132
+ return unless use_anonymous_forwarding?
133
+
134
+ rest_arg, kwrest_arg, _block_arg = *forwardable_args
118
135
 
119
- begin_pos = forwarding_method.source_range.begin_pos
120
- end_pos = forwarding_method.source_range.end_pos
136
+ send_classifications.each do |send_node, _c, forward_rest, forward_kwrest|
137
+ if forward_rest
138
+ register_forward_args_offense(def_node.arguments, rest_arg)
139
+ register_forward_args_offense(send_node, forward_rest)
140
+ end
121
141
 
122
- lvars.all? { |lvar| lvar.source_range.begin_pos.between?(begin_pos, end_pos) }
142
+ if forward_kwrest
143
+ register_forward_kwargs_offense(!forward_rest, def_node.arguments, kwrest_arg)
144
+ register_forward_kwargs_offense(!forward_rest, send_node, forward_kwrest)
145
+ end
146
+ end
123
147
  end
124
148
 
125
- def register_offense_to_forwarding_method_arguments(forwarding_method)
126
- add_offense(arguments_range(forwarding_method)) do |corrector|
127
- begin_pos = forwarding_method.loc.selector&.end_pos || forwarding_method.loc.dot.end_pos
128
- range = range_between(begin_pos, forwarding_method.source_range.end_pos)
149
+ def non_splat_or_block_pass_lvar_references(body)
150
+ body.each_descendant(:lvar, :lvasgn).filter_map do |lvar|
151
+ parent = lvar.parent
129
152
 
130
- corrector.replace(range, '(...)')
131
- end
153
+ next if lvar.lvar_type? && FORWARDING_LVAR_TYPES.include?(parent.type)
154
+
155
+ lvar.children.first
156
+ end.uniq
132
157
  end
133
158
 
134
- def register_offense_to_method_definition_arguments(method_definition)
135
- add_offense(arguments_range(method_definition)) do |corrector|
136
- arguments_range = range_with_surrounding_space(
137
- method_definition.arguments.source_range, side: :left
159
+ def classify_send_nodes(def_node, send_nodes, referenced_lvars, forwardable_args)
160
+ send_nodes.filter_map do |send_node|
161
+ classification_and_forwards = classification_and_forwards(
162
+ def_node,
163
+ send_node,
164
+ referenced_lvars,
165
+ forwardable_args
138
166
  )
139
- corrector.replace(arguments_range, '(...)')
167
+
168
+ next unless classification_and_forwards
169
+
170
+ [send_node, *classification_and_forwards]
140
171
  end
141
172
  end
142
173
 
143
- def arguments_range(node)
144
- arguments = node.arguments
174
+ def classification_and_forwards(def_node, send_node, referenced_lvars, forwardable_args)
175
+ classifier = SendNodeClassifier.new(
176
+ def_node,
177
+ send_node,
178
+ referenced_lvars,
179
+ forwardable_args,
180
+ target_ruby_version: target_ruby_version,
181
+ allow_only_rest_arguments: allow_only_rest_arguments?
182
+ )
183
+
184
+ classification = classifier.classification
145
185
 
146
- range_between(arguments.first.source_range.begin_pos, arguments.last.source_range.end_pos)
186
+ return unless classification
187
+
188
+ [classification, classifier.forwarded_rest_arg, classifier.forwarded_kwrest_arg]
189
+ end
190
+
191
+ def register_forward_args_offense(def_arguments_or_send, rest_arg_or_splat)
192
+ add_offense(rest_arg_or_splat, message: ARGS_MSG) do |corrector|
193
+ add_parens_if_missing(def_arguments_or_send, corrector)
194
+
195
+ corrector.replace(rest_arg_or_splat, '*')
196
+ end
197
+ end
198
+
199
+ def register_forward_kwargs_offense(add_parens, def_arguments_or_send, kwrest_arg_or_splat)
200
+ add_offense(kwrest_arg_or_splat, message: KWARGS_MSG) do |corrector|
201
+ add_parens_if_missing(def_arguments_or_send, corrector) if add_parens
202
+
203
+ corrector.replace(kwrest_arg_or_splat, '**')
204
+ end
205
+ end
206
+
207
+ def register_forward_all_offense(def_or_send, send_or_arguments, rest_or_splat)
208
+ arg_range = arguments_range(def_or_send, rest_or_splat)
209
+
210
+ add_offense(arg_range, message: FORWARDING_MSG) do |corrector|
211
+ add_parens_if_missing(send_or_arguments, corrector)
212
+
213
+ corrector.replace(arg_range, '...')
214
+ end
215
+ end
216
+
217
+ def arguments_range(node, first_node)
218
+ arguments = node.arguments.reject { |arg| ADDITIONAL_ARG_TYPES.include?(arg.type) }
219
+
220
+ start_node = first_node || arguments.first
221
+
222
+ range_between(start_node.source_range.begin_pos, arguments.last.source_range.end_pos)
147
223
  end
148
224
 
149
225
  def allow_only_rest_arguments?
150
226
  cop_config.fetch('AllowOnlyRestArgument', true)
151
227
  end
228
+
229
+ def use_anonymous_forwarding?
230
+ cop_config.fetch('UseAnonymousForwarding', false)
231
+ end
232
+
233
+ def add_parens_if_missing(node, corrector)
234
+ return if parentheses?(node)
235
+
236
+ add_parentheses(node, corrector)
237
+ end
238
+
239
+ # Classifies send nodes for possible rest/kwrest/all (including block) forwarding.
240
+ class SendNodeClassifier
241
+ extend NodePattern::Macros
242
+
243
+ # @!method forwarded_rest_arg?(node, rest_name)
244
+ def_node_matcher :forwarded_rest_arg?, '(splat (lvar %1))'
245
+
246
+ # @!method extract_forwarded_kwrest_arg(node, kwrest_name)
247
+ def_node_matcher :extract_forwarded_kwrest_arg, '(hash <$(kwsplat (lvar %1)) ...>)'
248
+
249
+ # @!method forwarded_block_arg?(node, block_name)
250
+ def_node_matcher :forwarded_block_arg?, '(block_pass {(lvar %1) nil?})'
251
+
252
+ def initialize(def_node, send_node, referenced_lvars, forwardable_args, **config)
253
+ @def_node = def_node
254
+ @send_node = send_node
255
+ @referenced_lvars = referenced_lvars
256
+ @rest_arg, @kwrest_arg, @block_arg = *forwardable_args
257
+ @rest_arg_name, @kwrest_arg_name, @block_arg_name =
258
+ *forwardable_args.map { |a| a&.name }
259
+ @config = config
260
+ end
261
+
262
+ def forwarded_rest_arg
263
+ return nil if referenced_rest_arg?
264
+
265
+ arguments.find { |arg| forwarded_rest_arg?(arg, @rest_arg_name) }
266
+ end
267
+
268
+ def forwarded_kwrest_arg
269
+ return nil if referenced_kwrest_arg?
270
+
271
+ arguments.filter_map { |arg| extract_forwarded_kwrest_arg(arg, @kwrest_arg_name) }.first
272
+ end
273
+
274
+ def forwarded_block_arg
275
+ return nil if referenced_block_arg?
276
+
277
+ arguments.find { |arg| forwarded_block_arg?(arg, @block_arg_name) }
278
+ end
279
+
280
+ def classification
281
+ return nil unless forwarded_rest_arg || forwarded_kwrest_arg
282
+
283
+ if can_forward_all?
284
+ :all
285
+ else
286
+ :rest_or_kwrest
287
+ end
288
+ end
289
+
290
+ private
291
+
292
+ def can_forward_all?
293
+ return false if any_arg_referenced?
294
+ return false if ruby_32_missing_rest_or_kwest?
295
+ return false unless offensive_block_forwarding?
296
+ return false if additional_kwargs_or_forwarded_kwargs?
297
+
298
+ no_additional_args? || (target_ruby_version >= 3.0 && no_post_splat_args?)
299
+ end
300
+
301
+ def ruby_32_missing_rest_or_kwest?
302
+ target_ruby_version >= 3.2 && !forwarded_rest_and_kwrest_args
303
+ end
304
+
305
+ def offensive_block_forwarding?
306
+ @block_arg ? forwarded_block_arg : allow_offense_for_no_block?
307
+ end
308
+
309
+ def forwarded_rest_and_kwrest_args
310
+ forwarded_rest_arg && forwarded_kwrest_arg
311
+ end
312
+
313
+ def arguments
314
+ @send_node.arguments
315
+ end
316
+
317
+ def referenced_rest_arg?
318
+ @referenced_lvars.include?(@rest_arg_name)
319
+ end
320
+
321
+ def referenced_kwrest_arg?
322
+ @referenced_lvars.include?(@kwrest_arg_name)
323
+ end
324
+
325
+ def referenced_block_arg?
326
+ @referenced_lvars.include?(@block_arg_name)
327
+ end
328
+
329
+ def any_arg_referenced?
330
+ referenced_rest_arg? || referenced_kwrest_arg? || referenced_block_arg?
331
+ end
332
+
333
+ def target_ruby_version
334
+ @config.fetch(:target_ruby_version)
335
+ end
336
+
337
+ def no_post_splat_args?
338
+ return true unless (splat_index = arguments.index(forwarded_rest_arg))
339
+
340
+ arg_after_splat = arguments[splat_index + 1]
341
+ [nil, :hash, :block_pass].include?(arg_after_splat&.type)
342
+ end
343
+
344
+ def additional_kwargs_or_forwarded_kwargs?
345
+ additional_kwargs? || forward_additional_kwargs?
346
+ end
347
+
348
+ def additional_kwargs?
349
+ @def_node.arguments.any? { |a| a.kwarg_type? || a.kwoptarg_type? }
350
+ end
351
+
352
+ def forward_additional_kwargs?
353
+ return false unless forwarded_kwrest_arg
354
+
355
+ !forwarded_kwrest_arg.parent.children.one?
356
+ end
357
+
358
+ def allow_offense_for_no_block?
359
+ !@config.fetch(:allow_only_rest_arguments)
360
+ end
361
+
362
+ def no_additional_args?
363
+ forwardable_count = [@rest_arg, @kwrest_arg, @block_arg].compact.size
364
+
365
+ @def_node.arguments.size == forwardable_count &&
366
+ @send_node.arguments.size == forwardable_count
367
+ end
368
+ end
152
369
  end
153
370
  end
154
371
  end
@@ -11,6 +11,15 @@ module RuboCop
11
11
  # The `array1.intersect?(array2)` method is faster than
12
12
  # `(array1 & array2).any?` and is more readable.
13
13
  #
14
+ # In cases like the following, compatibility is not ensured,
15
+ # so it will not be detected when using block argument.
16
+ #
17
+ # [source,ruby]
18
+ # ----
19
+ # ([1] & [1,2]).any? { |x| false } # => false
20
+ # [1].intersect?([1,2]) { |x| false } # => true
21
+ # ----
22
+ #
14
23
  # @safety
15
24
  # This cop cannot guarantee that `array1` and `array2` are
16
25
  # actually arrays while method `intersect?` is for arrays only.
@@ -68,16 +77,15 @@ module RuboCop
68
77
  RESTRICT_ON_SEND = (STRAIGHT_METHODS + NEGATED_METHODS).freeze
69
78
 
70
79
  def on_send(node)
80
+ return if (parent = node.parent) && (parent.block_type? || parent.numblock_type?)
71
81
  return unless (receiver, argument, method_name = bad_intersection_check?(node))
72
82
 
73
83
  message = message(receiver.source, argument.source, method_name)
74
84
 
75
85
  add_offense(node, message: message) do |corrector|
76
- if straight?(method_name)
77
- corrector.replace(node, "#{receiver.source}.intersect?(#{argument.source})")
78
- else
79
- corrector.replace(node, "!#{receiver.source}.intersect?(#{argument.source})")
80
- end
86
+ bang = straight?(method_name) ? '' : '!'
87
+
88
+ corrector.replace(node, "#{bang}#{receiver.source}.intersect?(#{argument.source})")
81
89
  end
82
90
  end
83
91
 
@@ -370,7 +370,8 @@ module RuboCop
370
370
  def special_method_proper_block_style?(node)
371
371
  method_name = node.method_name
372
372
  return true if allowed_method?(method_name) || matches_allowed_pattern?(method_name)
373
- return node.braces? if braces_required_method?(method_name)
373
+
374
+ node.braces? if braces_required_method?(method_name)
374
375
  end
375
376
 
376
377
  def braces_required_method?(method_name)
@@ -8,6 +8,11 @@ module RuboCop
8
8
  # `==`, `equal?`, and `eql?` custom method definitions are allowed by default.
9
9
  # These are customizable with `AllowedMethods` option.
10
10
  #
11
+ # @safety
12
+ # This cop's autocorrection is unsafe because there is no guarantee that
13
+ # the constant `Foo` exists when autocorrecting `var.class.name == 'Foo'` to
14
+ # `var.instance_of?(Foo)`.
15
+ #
11
16
  # @example
12
17
  # # bad
13
18
  # var.class == Date
@@ -69,6 +74,8 @@ module RuboCop
69
74
  matches_allowed_pattern?(def_node.method_name))
70
75
 
71
76
  class_comparison_candidate?(node) do |receiver_node, class_node|
77
+ return if class_node.dstr_type?
78
+
72
79
  range = offense_range(receiver_node, node)
73
80
  class_argument = (class_name = class_name(class_node, node)) ? "(#{class_name})" : ''
74
81
 
@@ -25,6 +25,7 @@ module RuboCop
25
25
  # # bad
26
26
  # items.collect
27
27
  # items.collect!
28
+ # items.collect_concat
28
29
  # items.inject
29
30
  # items.detect
30
31
  # items.find_all
@@ -33,6 +34,7 @@ module RuboCop
33
34
  # # good
34
35
  # items.map
35
36
  # items.map!
37
+ # items.flat_map
36
38
  # items.reduce
37
39
  # items.find
38
40
  # items.select
@@ -67,6 +67,7 @@ module RuboCop
67
67
  return unless node.parent&.begin_type?
68
68
  return unless collection_looping_method?(node)
69
69
  return unless same_collection_looping_block?(node, node.left_sibling)
70
+ return unless node.body && node.left_sibling.body
70
71
 
71
72
  add_offense(node) do |corrector|
72
73
  combine_with_left_sibling(corrector, node)
@@ -92,8 +93,9 @@ module RuboCop
92
93
  end
93
94
 
94
95
  def same_collection_looping_block?(node, sibling)
95
- (sibling&.block_type? || sibling&.numblock_type?) &&
96
- sibling.send_node.method?(node.method_name) &&
96
+ return false if sibling.nil? || (!sibling.block_type? && !sibling.numblock_type?)
97
+
98
+ sibling.method?(node.method_name) &&
97
99
  sibling.receiver == node.receiver &&
98
100
  sibling.send_node.arguments == node.send_node.arguments
99
101
  end
@@ -74,7 +74,7 @@ module RuboCop
74
74
  new_arguments =
75
75
  node.arguments.map do |arg|
76
76
  if arg.percent_literal?
77
- arg.children.map(&:value).map(&:inspect)
77
+ arg.children.map { |child| child.value.inspect }
78
78
  else
79
79
  arg.children.map(&:source)
80
80
  end
@@ -40,9 +40,13 @@ module RuboCop
40
40
  extend AutoCorrector
41
41
 
42
42
  MSG = 'Do not use empty `case` condition, instead use an `if` expression.'
43
+ NOT_SUPPORTED_PARENT_TYPES = %i[return break next send csend].freeze
43
44
 
45
+ # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
44
46
  def on_case(case_node)
45
- return if case_node.condition
47
+ if case_node.condition || NOT_SUPPORTED_PARENT_TYPES.include?(case_node.parent&.type)
48
+ return
49
+ end
46
50
 
47
51
  branch_bodies = [*case_node.when_branches.map(&:body), case_node.else_branch].compact
48
52
 
@@ -52,6 +56,7 @@ module RuboCop
52
56
 
53
57
  add_offense(case_node.loc.keyword) { |corrector| autocorrect(corrector, case_node) }
54
58
  end
59
+ # rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
55
60
 
56
61
  private
57
62
 
@@ -80,7 +80,7 @@ module RuboCop
80
80
  private
81
81
 
82
82
  def suspect_enumerable?(node)
83
- node.multiline? && node.send_node.method?(:each) && !node.send_node.arguments?
83
+ node.multiline? && node.method?(:each) && !node.send_node.arguments?
84
84
  end
85
85
  end
86
86
  end