rubocop 1.64.0 → 1.65.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 (63) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -2
  3. data/config/default.yml +11 -1
  4. data/lib/rubocop/config_loader.rb +1 -1
  5. data/lib/rubocop/config_loader_resolver.rb +9 -3
  6. data/lib/rubocop/cop/cop.rb +20 -2
  7. data/lib/rubocop/cop/force.rb +12 -0
  8. data/lib/rubocop/cop/gemspec/add_runtime_dependency.rb +38 -0
  9. data/lib/rubocop/cop/gemspec/duplicated_assignment.rb +2 -2
  10. data/lib/rubocop/cop/gemspec/ruby_version_globals_usage.rb +3 -3
  11. data/lib/rubocop/cop/layout/case_indentation.rb +1 -1
  12. data/lib/rubocop/cop/layout/empty_line_after_multiline_condition.rb +1 -1
  13. data/lib/rubocop/cop/layout/first_argument_indentation.rb +2 -2
  14. data/lib/rubocop/cop/layout/heredoc_indentation.rb +1 -1
  15. data/lib/rubocop/cop/layout/indentation_width.rb +1 -1
  16. data/lib/rubocop/cop/layout/line_length.rb +20 -20
  17. data/lib/rubocop/cop/layout/space_around_operators.rb +3 -0
  18. data/lib/rubocop/cop/layout/space_inside_string_interpolation.rb +3 -4
  19. data/lib/rubocop/cop/legacy/corrector.rb +12 -2
  20. data/lib/rubocop/cop/lint/duplicate_case_condition.rb +1 -1
  21. data/lib/rubocop/cop/lint/empty_when.rb +1 -1
  22. data/lib/rubocop/cop/lint/erb_new_arguments.rb +21 -14
  23. data/lib/rubocop/cop/lint/implicit_string_concatenation.rb +14 -7
  24. data/lib/rubocop/cop/lint/literal_as_condition.rb +1 -1
  25. data/lib/rubocop/cop/lint/nested_method_definition.rb +1 -1
  26. data/lib/rubocop/cop/lint/to_enum_arguments.rb +2 -9
  27. data/lib/rubocop/cop/lint/unmodified_reduce_accumulator.rb +1 -0
  28. data/lib/rubocop/cop/lint/void.rb +5 -0
  29. data/lib/rubocop/cop/metrics/block_nesting.rb +19 -7
  30. data/lib/rubocop/cop/mixin/alignment.rb +5 -1
  31. data/lib/rubocop/cop/mixin/allowed_methods.rb +7 -1
  32. data/lib/rubocop/cop/mixin/allowed_pattern.rb +15 -3
  33. data/lib/rubocop/cop/mixin/configurable_max.rb +5 -1
  34. data/lib/rubocop/cop/mixin/rescue_node.rb +4 -0
  35. data/lib/rubocop/cop/style/arguments_forwarding.rb +1 -1
  36. data/lib/rubocop/cop/style/copyright.rb +5 -2
  37. data/lib/rubocop/cop/style/documentation.rb +24 -24
  38. data/lib/rubocop/cop/style/hash_except.rb +8 -5
  39. data/lib/rubocop/cop/style/map_compact_with_conditional_block.rb +77 -43
  40. data/lib/rubocop/cop/style/method_call_with_args_parentheses/omit_parentheses.rb +5 -0
  41. data/lib/rubocop/cop/style/quoted_symbols.rb +1 -1
  42. data/lib/rubocop/cop/style/redundant_begin.rb +1 -1
  43. data/lib/rubocop/cop/style/redundant_file_extension_in_require.rb +1 -1
  44. data/lib/rubocop/cop/style/redundant_line_continuation.rb +2 -1
  45. data/lib/rubocop/cop/style/send_with_literal_method_name.rb +23 -2
  46. data/lib/rubocop/cop/style/super_arguments.rb +52 -15
  47. data/lib/rubocop/cop/style/symbol_proc.rb +8 -1
  48. data/lib/rubocop/cop/style/zero_length_predicate.rb +28 -24
  49. data/lib/rubocop/cop/team.rb +10 -0
  50. data/lib/rubocop/cop/util.rb +7 -1
  51. data/lib/rubocop/cops_documentation_generator.rb +1 -1
  52. data/lib/rubocop/ext/regexp_parser.rb +4 -21
  53. data/lib/rubocop/formatter/html_formatter.rb +3 -1
  54. data/lib/rubocop/lsp/routes.rb +1 -1
  55. data/lib/rubocop/rspec/shared_contexts.rb +20 -0
  56. data/lib/rubocop/rspec/support.rb +1 -0
  57. data/lib/rubocop/server/cache.rb +10 -0
  58. data/lib/rubocop/server/client_command/exec.rb +2 -2
  59. data/lib/rubocop/server/client_command/start.rb +1 -1
  60. data/lib/rubocop/server/core.rb +4 -0
  61. data/lib/rubocop/version.rb +1 -1
  62. data/lib/rubocop.rb +1 -0
  63. metadata +7 -6
@@ -3,12 +3,12 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Metrics
6
- # Checks for excessive nesting of conditional and looping
7
- # constructs.
6
+ # Checks for excessive nesting of conditional and looping constructs.
8
7
  #
9
- # You can configure if blocks are considered using the `CountBlocks`
10
- # option. When set to `false` (the default) blocks are not counted
11
- # towards the nesting level. Set to `true` to count blocks as well.
8
+ # You can configure if blocks are considered using the `CountBlocks` and `CountModifierForms`
9
+ # options. When both are set to `false` (the default) blocks and modifier forms are not
10
+ # counted towards the nesting level. Set them to `true` to include these in the nesting level
11
+ # calculation as well.
12
12
  #
13
13
  # The maximum level of nesting allowed is configurable.
14
14
  class BlockNesting < Base
@@ -27,7 +27,7 @@ module RuboCop
27
27
 
28
28
  def check_nesting_level(node, max, current_level)
29
29
  if consider_node?(node)
30
- current_level += 1 unless node.if_type? && node.elsif?
30
+ current_level += 1 if count_if_block?(node)
31
31
  if current_level > max
32
32
  self.max = current_level
33
33
  unless part_of_ignored_node?(node)
@@ -41,6 +41,14 @@ module RuboCop
41
41
  end
42
42
  end
43
43
 
44
+ def count_if_block?(node)
45
+ return true unless node.if_type?
46
+ return false if node.elsif?
47
+ return count_modifier_forms? if node.modifier_form?
48
+
49
+ true
50
+ end
51
+
44
52
  def consider_node?(node)
45
53
  return true if NESTING_BLOCKS.include?(node.type)
46
54
 
@@ -52,7 +60,11 @@ module RuboCop
52
60
  end
53
61
 
54
62
  def count_blocks?
55
- cop_config['CountBlocks']
63
+ cop_config.fetch('CountBlocks', false)
64
+ end
65
+
66
+ def count_modifier_forms?
67
+ cop_config.fetch('CountModifierForms', false)
56
68
  end
57
69
  end
58
70
  end
@@ -65,8 +65,12 @@ module RuboCop
65
65
  inner.begin_pos >= outer.begin_pos && inner.end_pos <= outer.end_pos
66
66
  end
67
67
 
68
- # @deprecated Use processed_source.comment_at_line(line)
68
+ # @deprecated Use processed_source.line_with_comment?(line)
69
69
  def end_of_line_comment(line)
70
+ warn Rainbow(<<~WARNING).yellow, uplevel: 1
71
+ `end_of_line_comment` is deprecated. Use `processed_source.line_with_comment?` instead.
72
+ WARNING
73
+
70
74
  processed_source.line_with_comment?(line)
71
75
  end
72
76
 
@@ -15,7 +15,13 @@ module RuboCop
15
15
  end
16
16
 
17
17
  # @deprecated Use allowed_method? instead
18
- alias ignored_method? allowed_method?
18
+ def ignored_method?
19
+ warn Rainbow(<<~WARNING).yellow, uplevel: 1
20
+ `ignored_method?` is deprecated. Use `allowed_method?` instead.
21
+ WARNING
22
+
23
+ allowed_method?
24
+ end
19
25
 
20
26
  # @api public
21
27
  def allowed_methods
@@ -18,14 +18,26 @@ module RuboCop
18
18
  end
19
19
 
20
20
  # @deprecated Use allowed_line? instead
21
- alias ignored_line? allowed_line?
21
+ def ignored_line?
22
+ warn Rainbow(<<~WARNING).yellow, uplevel: 1
23
+ `ignored_line?` is deprecated. Use `allowed_line?` instead.
24
+ WARNING
25
+
26
+ allowed_line?
27
+ end
22
28
 
23
29
  def matches_allowed_pattern?(line)
24
30
  allowed_patterns.any? { |pattern| Regexp.new(pattern).match?(line) }
25
31
  end
26
32
 
27
- # @deprecated Use matches_allowed_pattern?? instead
28
- alias matches_ignored_pattern? matches_allowed_pattern?
33
+ # @deprecated Use matches_allowed_pattern? instead
34
+ def matches_ignored_pattern?
35
+ warn Rainbow(<<~WARNING).yellow, uplevel: 1
36
+ `matches_ignored_pattern?` is deprecated. Use `matches_allowed_pattern?` instead.
37
+ WARNING
38
+
39
+ matches_allowed_pattern?
40
+ end
29
41
 
30
42
  def allowed_patterns
31
43
  # Since there could be a pattern specified in the default config, merge the two
@@ -4,11 +4,15 @@ module RuboCop
4
4
  module Cop
5
5
  # Handles `Max` configuration parameters, especially setting them to an
6
6
  # appropriate value with --auto-gen-config.
7
- # @deprecated Use `exclude_limit ParameterName` instead.
7
+ # @deprecated Use `exclude_limit <ParameterName>` instead.
8
8
  module ConfigurableMax
9
9
  private
10
10
 
11
11
  def max=(value)
12
+ warn Rainbow(<<~WARNING).yellow, uplevel: 1
13
+ `max=` is deprecated. Use `exclude_limit <ParameterName>` instead.
14
+ WARNING
15
+
12
16
  cfg = config_to_allow_offenses
13
17
  cfg[:exclude_limit] ||= {}
14
18
  current_max = cfg[:exclude_limit][max_parameter_name]
@@ -18,6 +18,10 @@ module RuboCop
18
18
 
19
19
  # @deprecated Use ResbodyNode#exceptions instead
20
20
  def rescued_exceptions(resbody)
21
+ warn Rainbow(<<~WARNING).yellow, uplevel: 1
22
+ `rescued_exceptions` is deprecated. Use `ResbodyNode#exceptions` instead.
23
+ WARNING
24
+
21
25
  rescue_group, = *resbody
22
26
  if rescue_group
23
27
  rescue_group.values
@@ -148,7 +148,7 @@ module RuboCop
148
148
 
149
149
  restarg, kwrestarg, blockarg = extract_forwardable_args(node.arguments)
150
150
  forwardable_args = redundant_forwardable_named_args(restarg, kwrestarg, blockarg)
151
- send_nodes = node.each_descendant(:send, :csend, :super).to_a
151
+ send_nodes = node.each_descendant(:send, :csend, :super, :yield).to_a
152
152
 
153
153
  send_classifications = classify_send_nodes(
154
154
  node, send_nodes, non_splat_or_block_pass_lvar_references(node.body), forwardable_args
@@ -58,12 +58,15 @@ module RuboCop
58
58
  end
59
59
 
60
60
  def verify_autocorrect_notice!
61
- raise Warning, AUTOCORRECT_EMPTY_WARNING if autocorrect_notice.empty?
61
+ if autocorrect_notice.nil? || autocorrect_notice.empty?
62
+ raise Warning, "#{cop_name}: #{AUTOCORRECT_EMPTY_WARNING}"
63
+ end
62
64
 
63
65
  regex = Regexp.new(notice)
64
66
  return if autocorrect_notice.gsub(/^# */, '').match?(regex)
65
67
 
66
- raise Warning, "AutocorrectNotice '#{autocorrect_notice}' must match Notice /#{notice}/"
68
+ message = "AutocorrectNotice '#{autocorrect_notice}' must match Notice /#{notice}/"
69
+ raise Warning, "#{cop_name}: #{message}"
67
70
  end
68
71
 
69
72
  def insert_notice_before(processed_source)
@@ -29,36 +29,36 @@ module RuboCop
29
29
  # end
30
30
  #
31
31
  # # allowed
32
- # # Class without body
32
+ # # Class without body
33
+ # class Person
34
+ # end
35
+ #
36
+ # # Namespace - A namespace can be a class or a module
37
+ # # Containing a class
38
+ # module Namespace
39
+ # # Description/Explanation of Person class
33
40
  # class Person
41
+ # # ...
34
42
  # end
43
+ # end
35
44
  #
36
- # # Namespace - A namespace can be a class or a module
37
- # # Containing a class
38
- # module Namespace
39
- # # Description/Explanation of Person class
40
- # class Person
41
- # # ...
42
- # end
45
+ # # Containing constant visibility declaration
46
+ # module Namespace
47
+ # class Private
43
48
  # end
44
49
  #
45
- # # Containing constant visibility declaration
46
- # module Namespace
47
- # class Private
48
- # end
49
- #
50
- # private_constant :Private
51
- # end
50
+ # private_constant :Private
51
+ # end
52
52
  #
53
- # # Containing constant definition
54
- # module Namespace
55
- # Public = Class.new
56
- # end
53
+ # # Containing constant definition
54
+ # module Namespace
55
+ # Public = Class.new
56
+ # end
57
57
  #
58
- # # Macro calls
59
- # module Namespace
60
- # extend Foo
61
- # end
58
+ # # Macro calls
59
+ # module Namespace
60
+ # extend Foo
61
+ # end
62
62
  #
63
63
  # @example AllowedConstants: ['ClassMethods']
64
64
  #
@@ -67,7 +67,7 @@ module RuboCop
67
67
  # module ClassMethods
68
68
  # # ...
69
69
  # end
70
- # end
70
+ # end
71
71
  #
72
72
  class Documentation < Base
73
73
  include DocumentationComment
@@ -23,9 +23,9 @@ module RuboCop
23
23
  # {foo: 1, bar: 2, baz: 3}.reject {|k, v| k == :bar }
24
24
  # {foo: 1, bar: 2, baz: 3}.select {|k, v| k != :bar }
25
25
  # {foo: 1, bar: 2, baz: 3}.filter {|k, v| k != :bar }
26
- # {foo: 1, bar: 2, baz: 3}.reject {|k, v| %i[foo bar].include?(k) }
27
- # {foo: 1, bar: 2, baz: 3}.select {|k, v| !%i[foo bar].include?(k) }
28
- # {foo: 1, bar: 2, baz: 3}.filter {|k, v| !%i[foo bar].include?(k) }
26
+ # {foo: 1, bar: 2, baz: 3}.reject {|k, v| %i[bar].include?(k) }
27
+ # {foo: 1, bar: 2, baz: 3}.select {|k, v| !%i[bar].include?(k) }
28
+ # {foo: 1, bar: 2, baz: 3}.filter {|k, v| !%i[bar].include?(k) }
29
29
  #
30
30
  # # good
31
31
  # {foo: 1, bar: 2, baz: 3}.except(:bar)
@@ -73,8 +73,9 @@ module RuboCop
73
73
  PATTERN
74
74
 
75
75
  def on_send(node)
76
+ method_name = node.method_name
76
77
  block = node.parent
77
- return unless bad_method?(block) && semantically_except_method?(node, block)
78
+ return unless bad_method?(method_name, block) && semantically_except_method?(node, block)
78
79
 
79
80
  except_key = except_key(block)
80
81
  return if except_key.nil? || !safe_to_register_offense?(block, except_key)
@@ -91,7 +92,7 @@ module RuboCop
91
92
  private
92
93
 
93
94
  # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
94
- def bad_method?(block)
95
+ def bad_method?(method_name, block)
95
96
  if active_support_extensions_enabled?
96
97
  bad_method_with_active_support?(block) do |key_arg, send_node|
97
98
  if send_node.method?(:in?) && send_node.receiver&.source != key_arg.source
@@ -103,6 +104,8 @@ module RuboCop
103
104
  end
104
105
  else
105
106
  bad_method_with_poro?(block) do |key_arg, send_node|
107
+ return false if method_name == :reject && block.body.method?(:!)
108
+
106
109
  !send_node.method?(:include?) || send_node.first_argument&.source == key_arg.source
107
110
  end
108
111
  end
@@ -4,6 +4,7 @@ module RuboCop
4
4
  module Cop
5
5
  module Style
6
6
  # Prefer `select` or `reject` over `map { ... }.compact`.
7
+ # This cop also handles `filter_map { ... }`, similar to `map { ... }.compact`.
7
8
  #
8
9
  # @example
9
10
  #
@@ -11,6 +12,9 @@ module RuboCop
11
12
  # array.map { |e| some_condition? ? e : next }.compact
12
13
  #
13
14
  # # bad
15
+ # array.filter_map { |e| some_condition? ? e : next }
16
+ #
17
+ # # bad
14
18
  # array.map do |e|
15
19
  # if some_condition?
16
20
  # e
@@ -40,55 +44,73 @@ module RuboCop
40
44
  class MapCompactWithConditionalBlock < Base
41
45
  extend AutoCorrector
42
46
 
43
- MSG = 'Replace `map { ... }.compact` with `%<method>s`.'
44
-
45
- # @!method map_and_compact?(node)
46
- def_node_matcher :map_and_compact?, <<~RUBY
47
- (call
48
- (block
49
- (call _ :map)
50
- (args
51
- $(arg _))
52
- {
53
- (if $_ $(lvar _) {next nil?})
54
- (if $_ {next nil?} $(lvar _))
55
- (if $_ (next $(lvar _)) {next nil nil?})
56
- (if $_ {next nil nil?} (next $(lvar _)))
57
- (begin
58
- {
59
- (if $_ next nil?)
60
- (if $_ nil? next)
61
- }
62
- $(lvar _))
63
- (begin
64
- {
65
- (if $_ (next $(lvar _)) nil?)
66
- (if $_ nil? (next $(lvar _)))
67
- }
68
- (nil))
69
- }) :compact)
47
+ MSG = 'Replace `%<current>s` with `%<method>s`.'
48
+ RESTRICT_ON_SEND = %i[compact filter_map].freeze
49
+
50
+ # @!method conditional_block(node)
51
+ def_node_matcher :conditional_block, <<~RUBY
52
+ (block
53
+ (call _ {:map :filter_map})
54
+ (args
55
+ $(arg _))
56
+ {
57
+ (if $_ $(lvar _) {next nil?})
58
+ (if $_ {next nil?} $(lvar _))
59
+ (if $_ (next $(lvar _)) {next nil nil?})
60
+ (if $_ {next nil nil?} (next $(lvar _)))
61
+ (begin
62
+ {
63
+ (if $_ next nil?)
64
+ (if $_ nil? next)
65
+ }
66
+ $(lvar _))
67
+ (begin
68
+ {
69
+ (if $_ (next $(lvar _)) nil?)
70
+ (if $_ nil? (next $(lvar _)))
71
+ }
72
+ (nil))
73
+ })
70
74
  RUBY
71
75
 
72
76
  def on_send(node)
73
- map_and_compact?(node) do |block_argument_node, condition_node, return_value_node|
74
- return unless returns_block_argument?(block_argument_node, return_value_node)
75
- return if condition_node.parent.elsif?
76
-
77
- method = truthy_branch?(return_value_node) ? 'select' : 'reject'
78
- range = range(node)
79
-
80
- add_offense(range, message: format(MSG, method: method)) do |corrector|
81
- corrector.replace(
82
- range,
83
- "#{method} { |#{block_argument_node.source}| #{condition_node.source} }"
84
- )
85
- end
77
+ map_candidate = node.children.first
78
+ if (block_argument, condition, return_value = conditional_block(map_candidate))
79
+ return unless node.method?(:compact) && node.arguments.empty?
80
+
81
+ range = map_with_compact_range(node)
82
+ elsif (block_argument, condition, return_value = conditional_block(node.parent))
83
+ return unless node.method?(:filter_map)
84
+
85
+ range = filter_map_range(node)
86
+ else
87
+ return
86
88
  end
89
+
90
+ inspect(node, block_argument, condition, return_value, range)
87
91
  end
88
92
  alias on_csend on_send
89
93
 
90
94
  private
91
95
 
96
+ def inspect(node, block_argument_node, condition_node, return_value_node, range)
97
+ return unless returns_block_argument?(block_argument_node, return_value_node)
98
+ return if condition_node.parent.elsif?
99
+
100
+ method = truthy_branch?(return_value_node) ? 'select' : 'reject'
101
+ current = current(node)
102
+
103
+ add_offense(range, message: format(MSG, current: current, method: method)) do |corrector|
104
+ return if part_of_ignored_node?(node) || ignored_node?(node)
105
+
106
+ corrector.replace(
107
+ range, "#{method} { |#{block_argument_node.source}| #{condition_node.source} }"
108
+ )
109
+
110
+ ignore_node(node)
111
+ end
112
+ end
113
+
92
114
  def returns_block_argument?(block_argument_node, return_value_node)
93
115
  block_argument_node.name == return_value_node.children.first
94
116
  end
@@ -123,10 +145,22 @@ module RuboCop
123
145
  end
124
146
  end
125
147
 
126
- def range(node)
127
- map_node = node.receiver.send_node
148
+ def current(node)
149
+ if node.method?(:compact)
150
+ map_or_filter_map_method = node.children.first
151
+
152
+ "#{map_or_filter_map_method.method_name} { ... }.compact"
153
+ else
154
+ 'filter_map { ... }'
155
+ end
156
+ end
157
+
158
+ def map_with_compact_range(node)
159
+ node.receiver.send_node.loc.selector.begin.join(node.source_range.end)
160
+ end
128
161
 
129
- map_node.loc.selector.join(node.source_range.end)
162
+ def filter_map_range(node)
163
+ node.loc.selector.join(node.parent.source_range.end)
130
164
  end
131
165
  end
132
166
  end
@@ -18,6 +18,7 @@ module RuboCop
18
18
  return if inside_endless_method_def?(node)
19
19
  return if require_parentheses_for_hash_value_omission?(node)
20
20
  return if syntax_like_method_call?(node)
21
+ return if method_call_before_constant_resolution?(node)
21
22
  return if super_call_without_arguments?(node)
22
23
  return if legitimate_call_with_parentheses?(node)
23
24
  return if allowed_camel_case_method_call?(node)
@@ -63,6 +64,10 @@ module RuboCop
63
64
  node.implicit_call? || node.operator_method?
64
65
  end
65
66
 
67
+ def method_call_before_constant_resolution?(node)
68
+ node.parent&.const_type?
69
+ end
70
+
66
71
  def super_call_without_arguments?(node)
67
72
  node.super_type? && node.arguments.none?
68
73
  end
@@ -9,7 +9,7 @@ module RuboCop
9
9
  #
10
10
  # String interpolation is always kept in double quotes.
11
11
  #
12
- # Note: `Lint/SymbolConversion` can be used in parallel to ensure that symbols
12
+ # NOTE: `Lint/SymbolConversion` can be used in parallel to ensure that symbols
13
13
  # are not quoted that don't need to be. This cop is for configuring the quoting
14
14
  # style to use for symbols that require quotes.
15
15
  #
@@ -75,7 +75,7 @@ module RuboCop
75
75
 
76
76
  def on_def(node)
77
77
  return unless node.body&.kwbegin_type?
78
- return if node.endless? && !node.body.children.one?
78
+ return if node.endless?
79
79
 
80
80
  register_offense(node.body)
81
81
  end
@@ -6,7 +6,7 @@ module RuboCop
6
6
  # Checks for the presence of superfluous `.rb` extension in
7
7
  # the filename provided to `require` and `require_relative`.
8
8
  #
9
- # Note: If the extension is omitted, Ruby tries adding '.rb', '.so',
9
+ # NOTE: If the extension is omitted, Ruby tries adding '.rb', '.so',
10
10
  # and so on to the name until found. If the file named cannot be found,
11
11
  # a `LoadError` will be raised.
12
12
  # There is an edge case where `foo.so` file is loaded instead of a `LoadError`
@@ -74,6 +74,7 @@ module RuboCop
74
74
  kFALSE kNIL kSELF kTRUE tCONSTANT tCVAR tFLOAT tGVAR tIDENTIFIER tINTEGER tIVAR
75
75
  tLBRACK tLCURLY tLPAREN_ARG tSTRING tSTRING_BEG tSYMBOL tXSTRING_BEG
76
76
  ].freeze
77
+ ARGUMENT_TAKING_FLOW_TOKEN_TYPES = %i[tIDENTIFIER kRETURN kBREAK kNEXT kYIELD].freeze
77
78
 
78
79
  def on_new_investigation
79
80
  return unless processed_source.ast
@@ -137,7 +138,7 @@ module RuboCop
137
138
  # do_something \
138
139
  # argument
139
140
  def method_with_argument?(current_token, next_token)
140
- return false if current_token.type != :tIDENTIFIER && current_token.type != :kRETURN
141
+ return false unless ARGUMENT_TAKING_FLOW_TOKEN_TYPES.include?(current_token.type)
141
142
 
142
143
  ARGUMENT_TYPES.include?(next_token.type)
143
144
  end
@@ -7,6 +7,20 @@ module RuboCop
7
7
  # Since the `send` method can be used to call private methods, by default,
8
8
  # only the `public_send` method is detected.
9
9
  #
10
+ # NOTE: Writer methods with names ending in `=` are always permitted because their
11
+ # behavior differs as follows:
12
+ #
13
+ # [source,ruby]
14
+ # ----
15
+ # def foo=(foo)
16
+ # @foo = foo
17
+ # 42
18
+ # end
19
+ #
20
+ # self.foo = 1 # => 1
21
+ # send(:foo=, 1) # => 42
22
+ # ----
23
+ #
10
24
  # @safety
11
25
  # This cop is not safe because it can incorrectly detect based on the receiver.
12
26
  # Additionally, when `AllowSend` is set to `true`, it cannot determine whether
@@ -43,8 +57,14 @@ module RuboCop
43
57
  MSG = 'Use `%<method_name>s` method call directly instead.'
44
58
  RESTRICT_ON_SEND = %i[public_send send __send__].freeze
45
59
  STATIC_METHOD_NAME_NODE_TYPES = %i[sym str].freeze
60
+ METHOD_NAME_PATTERN = /\A[a-zA-Z_][a-zA-Z0-9_]*[!?]?\z/.freeze
61
+ RESERVED_WORDS = %i[
62
+ BEGIN END alias and begin break case class def defined? do else elsif end ensure
63
+ false for if in module next nil not or redo rescue retry return self super then true
64
+ undef unless until when while yield
65
+ ].freeze
46
66
 
47
- # rubocop:disable Metrics/AbcSize
67
+ # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
48
68
  def on_send(node)
49
69
  return if allow_send? && !node.method?(:public_send)
50
70
  return unless (first_argument = node.first_argument)
@@ -52,6 +72,7 @@ module RuboCop
52
72
 
53
73
  offense_range = offense_range(node)
54
74
  method_name = first_argument.value
75
+ return if !METHOD_NAME_PATTERN.match?(method_name) || RESERVED_WORDS.include?(method_name)
55
76
 
56
77
  add_offense(offense_range, message: format(MSG, method_name: method_name)) do |corrector|
57
78
  if node.arguments.one?
@@ -62,7 +83,7 @@ module RuboCop
62
83
  end
63
84
  end
64
85
  end
65
- # rubocop:enable Metrics/AbcSize
86
+ # rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
66
87
 
67
88
  private
68
89