rubocop 1.22.2 → 1.24.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (95) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -2
  3. data/config/default.yml +87 -5
  4. data/lib/rubocop/cli/command/auto_genenerate_config.rb +1 -1
  5. data/lib/rubocop/cli/command/init_dotfile.rb +1 -1
  6. data/lib/rubocop/cli/command/show_docs_url.rb +48 -0
  7. data/lib/rubocop/cli/command/suggest_extensions.rb +1 -1
  8. data/lib/rubocop/cli.rb +1 -0
  9. data/lib/rubocop/config_loader_resolver.rb +1 -1
  10. data/lib/rubocop/cop/bundler/duplicated_gem.rb +1 -1
  11. data/lib/rubocop/cop/correctors/each_to_for_corrector.rb +1 -1
  12. data/lib/rubocop/cop/correctors/if_then_corrector.rb +55 -0
  13. data/lib/rubocop/cop/documentation.rb +19 -2
  14. data/lib/rubocop/cop/gemspec/date_assignment.rb +2 -10
  15. data/lib/rubocop/cop/gemspec/duplicated_assignment.rb +1 -10
  16. data/lib/rubocop/cop/gemspec/require_mfa.rb +144 -0
  17. data/lib/rubocop/cop/gemspec/ruby_version_globals_usage.rb +3 -10
  18. data/lib/rubocop/cop/generator.rb +1 -1
  19. data/lib/rubocop/cop/internal_affairs/redundant_method_dispatch_node.rb +47 -0
  20. data/lib/rubocop/cop/internal_affairs/undefined_config.rb +3 -1
  21. data/lib/rubocop/cop/internal_affairs.rb +1 -0
  22. data/lib/rubocop/cop/layout/block_alignment.rb +3 -3
  23. data/lib/rubocop/cop/layout/comment_indentation.rb +31 -2
  24. data/lib/rubocop/cop/layout/dot_position.rb +9 -1
  25. data/lib/rubocop/cop/layout/empty_line_between_defs.rb +22 -1
  26. data/lib/rubocop/cop/layout/hash_alignment.rb +1 -1
  27. data/lib/rubocop/cop/layout/space_after_colon.rb +1 -1
  28. data/lib/rubocop/cop/layout/space_before_first_arg.rb +4 -0
  29. data/lib/rubocop/cop/layout/space_in_lambda_literal.rb +11 -5
  30. data/lib/rubocop/cop/lint/ambiguous_range.rb +2 -2
  31. data/lib/rubocop/cop/lint/constant_definition_in_block.rb +1 -1
  32. data/lib/rubocop/cop/lint/deprecated_class_methods.rb +16 -4
  33. data/lib/rubocop/cop/lint/deprecated_constants.rb +3 -2
  34. data/lib/rubocop/cop/lint/deprecated_open_ssl_constant.rb +6 -0
  35. data/lib/rubocop/cop/lint/each_with_object_argument.rb +1 -1
  36. data/lib/rubocop/cop/lint/incompatible_io_select_with_fiber_scheduler.rb +4 -0
  37. data/lib/rubocop/cop/lint/number_conversion.rb +5 -2
  38. data/lib/rubocop/cop/lint/parentheses_as_grouped_expression.rb +7 -4
  39. data/lib/rubocop/cop/lint/useless_ruby2_keywords.rb +117 -0
  40. data/lib/rubocop/cop/metrics/block_length.rb +1 -0
  41. data/lib/rubocop/cop/metrics/cyclomatic_complexity.rb +0 -9
  42. data/lib/rubocop/cop/metrics/method_length.rb +1 -0
  43. data/lib/rubocop/cop/metrics/module_length.rb +1 -1
  44. data/lib/rubocop/cop/metrics/parameter_lists.rb +1 -1
  45. data/lib/rubocop/cop/metrics/utils/code_length_calculator.rb +1 -1
  46. data/lib/rubocop/cop/metrics/utils/repeated_attribute_discount.rb +1 -1
  47. data/lib/rubocop/cop/mixin/enforce_superclass.rb +5 -0
  48. data/lib/rubocop/cop/mixin/gemspec_help.rb +30 -0
  49. data/lib/rubocop/cop/mixin/hash_alignment_styles.rb +4 -3
  50. data/lib/rubocop/cop/mixin/hash_shorthand_syntax.rb +56 -0
  51. data/lib/rubocop/cop/mixin/string_literals_help.rb +1 -5
  52. data/lib/rubocop/cop/naming/block_forwarding.rb +107 -0
  53. data/lib/rubocop/cop/naming/file_name.rb +37 -4
  54. data/lib/rubocop/cop/security/open.rb +11 -1
  55. data/lib/rubocop/cop/style/character_literal.rb +8 -1
  56. data/lib/rubocop/cop/style/collection_compact.rb +31 -13
  57. data/lib/rubocop/cop/style/combinable_loops.rb +2 -2
  58. data/lib/rubocop/cop/style/documentation.rb +1 -1
  59. data/lib/rubocop/cop/style/empty_case_condition.rb +10 -0
  60. data/lib/rubocop/cop/style/empty_method.rb +1 -1
  61. data/lib/rubocop/cop/style/file_read.rb +112 -0
  62. data/lib/rubocop/cop/style/file_write.rb +124 -0
  63. data/lib/rubocop/cop/style/format_string_token.rb +2 -1
  64. data/lib/rubocop/cop/style/hash_conversion.rb +2 -1
  65. data/lib/rubocop/cop/style/hash_syntax.rb +22 -0
  66. data/lib/rubocop/cop/style/hash_transform_keys.rb +6 -6
  67. data/lib/rubocop/cop/style/hash_transform_values.rb +6 -6
  68. data/lib/rubocop/cop/style/if_inside_else.rb +15 -0
  69. data/lib/rubocop/cop/style/map_to_hash.rb +68 -0
  70. data/lib/rubocop/cop/style/method_call_with_args_parentheses/omit_parentheses.rb +13 -0
  71. data/lib/rubocop/cop/style/method_call_with_args_parentheses.rb +3 -1
  72. data/lib/rubocop/cop/style/method_def_parentheses.rb +17 -13
  73. data/lib/rubocop/cop/style/numeric_literals.rb +10 -1
  74. data/lib/rubocop/cop/style/one_line_conditional.rb +18 -39
  75. data/lib/rubocop/cop/style/open_struct_use.rb +69 -0
  76. data/lib/rubocop/cop/style/parentheses_around_condition.rb +12 -2
  77. data/lib/rubocop/cop/style/quoted_symbols.rb +11 -1
  78. data/lib/rubocop/cop/style/redundant_interpolation.rb +17 -3
  79. data/lib/rubocop/cop/style/redundant_regexp_character_class.rb +5 -1
  80. data/lib/rubocop/cop/style/redundant_self.rb +1 -1
  81. data/lib/rubocop/cop/style/safe_navigation.rb +1 -5
  82. data/lib/rubocop/cop/style/select_by_regexp.rb +4 -3
  83. data/lib/rubocop/cop/style/single_line_block_params.rb +2 -2
  84. data/lib/rubocop/cop/style/sole_nested_conditional.rb +3 -1
  85. data/lib/rubocop/cop/team.rb +1 -1
  86. data/lib/rubocop/cop/util.rb +9 -1
  87. data/lib/rubocop/formatter/html_formatter.rb +5 -2
  88. data/lib/rubocop/formatter/json_formatter.rb +4 -1
  89. data/lib/rubocop/options.rb +6 -1
  90. data/lib/rubocop/remote_config.rb +2 -4
  91. data/lib/rubocop/result_cache.rb +1 -1
  92. data/lib/rubocop/version.rb +1 -1
  93. data/lib/rubocop/yaml_duplication_checker.rb +1 -1
  94. data/lib/rubocop.rb +11 -0
  95. metadata +18 -5
@@ -135,7 +135,7 @@ module RuboCop
135
135
 
136
136
  def extract_body(node)
137
137
  case node.type
138
- when :class, :module, :block, :def, :defs
138
+ when :class, :module, :block, :numblock, :def, :defs
139
139
  node.body
140
140
  when :casgn
141
141
  _scope, _name, value = *node
@@ -59,7 +59,7 @@ module RuboCop
59
59
 
60
60
  # @!method attribute_call?(node)
61
61
  def_node_matcher :attribute_call?, <<~PATTERN
62
- ( {csend send} _receiver _method # and no parameters
62
+ (call _receiver _method # and no parameters
63
63
  )
64
64
  PATTERN
65
65
 
@@ -13,6 +13,11 @@ module RuboCop
13
13
  # @api private
14
14
  module EnforceSuperclass
15
15
  def self.included(base)
16
+ warn Rainbow(
17
+ '`RuboCop::Cop::EnforceSuperclass` is deprecated and will be removed in RuboCop 2.0. ' \
18
+ 'Please upgrade to RuboCop Rails 2.9 or newer to continue.'
19
+ ).yellow
20
+
16
21
  # @!method class_definition(node)
17
22
  base.def_node_matcher :class_definition, <<~PATTERN
18
23
  (class (const _ !:#{base::SUPERCLASS}) #{base::BASE_PATTERN} ...)
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ # Common functionality for checking gem declarations.
6
+ module GemspecHelp
7
+ extend NodePattern::Macros
8
+
9
+ # @!method gem_specification?(node)
10
+ def_node_matcher :gem_specification?, <<~PATTERN
11
+ (block
12
+ (send
13
+ (const
14
+ (const {cbase nil?} :Gem) :Specification) :new)
15
+ (args
16
+ (arg $_)) ...)
17
+ PATTERN
18
+
19
+ # @!method gem_specification(node)
20
+ def_node_search :gem_specification, <<~PATTERN
21
+ (block
22
+ (send
23
+ (const
24
+ (const {cbase nil?} :Gem) :Specification) :new)
25
+ (args
26
+ (arg $_)) ...)
27
+ PATTERN
28
+ end
29
+ end
30
+ end
@@ -43,7 +43,7 @@ module RuboCop
43
43
  end
44
44
 
45
45
  def value_delta(pair)
46
- return 0 if pair.value_on_new_line?
46
+ return 0 if pair.value_on_new_line? || pair.value_omission?
47
47
 
48
48
  correct_value_column = pair.loc.operator.end.column + 1
49
49
  actual_value_column = pair.value.loc.column
@@ -111,7 +111,8 @@ module RuboCop
111
111
  correct_value_column = first_pair.key.loc.column +
112
112
  current_pair.delimiter(true).length +
113
113
  max_key_width
114
- correct_value_column - current_pair.value.loc.column
114
+
115
+ current_pair.value_omission? ? 0 : correct_value_column - current_pair.value.loc.column
115
116
  end
116
117
  end
117
118
 
@@ -134,7 +135,7 @@ module RuboCop
134
135
  end
135
136
 
136
137
  def value_delta(first_pair, current_pair)
137
- first_pair.value_delta(current_pair)
138
+ current_pair.value_omission? ? 0 : first_pair.value_delta(current_pair)
138
139
  end
139
140
  end
140
141
 
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ # This module checks for Ruby 3.1's hash value omission syntax.
6
+ module HashShorthandSyntax
7
+ OMIT_HASH_VALUE_MSG = 'Omit the hash value.'
8
+ EXPLICIT_HASH_VALUE_MSG = 'Explicit the hash value.'
9
+
10
+ def on_pair(node)
11
+ return if target_ruby_version <= 3.0
12
+
13
+ hash_key_source = node.key.source
14
+
15
+ if enforced_shorthand_syntax == 'always'
16
+ return if node.value_omission? || require_hash_value?(hash_key_source, node)
17
+
18
+ message = OMIT_HASH_VALUE_MSG
19
+ replacement = "#{hash_key_source}:"
20
+ else
21
+ return unless node.value_omission?
22
+
23
+ message = EXPLICIT_HASH_VALUE_MSG
24
+ replacement = "#{hash_key_source}: #{hash_key_source}"
25
+ end
26
+
27
+ add_offense(node.value, message: message) do |corrector|
28
+ corrector.replace(node, replacement)
29
+ end
30
+ end
31
+
32
+ private
33
+
34
+ def enforced_shorthand_syntax
35
+ cop_config.fetch('EnforcedShorthandSyntax', 'always')
36
+ end
37
+
38
+ def require_hash_value?(hash_key_source, node)
39
+ return true if without_parentheses_call_expr_follows?(node)
40
+
41
+ hash_value = node.value
42
+ return true unless hash_value.send_type? || hash_value.lvar_type?
43
+
44
+ hash_key_source != hash_value.source || hash_key_source.end_with?('!', '?')
45
+ end
46
+
47
+ def without_parentheses_call_expr_follows?(node)
48
+ return false unless (ancestor = node.parent.parent)
49
+ return false unless (right_sibling = ancestor.right_sibling)
50
+
51
+ ancestor.respond_to?(:parenthesized?) && !ancestor.parenthesized? &&
52
+ right_sibling.respond_to?(:parenthesized?) && !right_sibling.parenthesized?
53
+ end
54
+ end
55
+ end
56
+ end
@@ -13,11 +13,7 @@ module RuboCop
13
13
  if style == :single_quotes
14
14
  !double_quotes_required?(src)
15
15
  else
16
- # The string needs single quotes if:
17
- # 1. It contains a double quote
18
- # 2. It contains text that would become an escape sequence with double quotes
19
- # 3. It contains text that would become an interpolation with double quotes
20
- !/" | (?<!\\)\\[abcefMnrtuUx0-7] | \#[@{$]/x.match?(src)
16
+ !/" | \\[^'\\] | \#[@{$]/x.match?(src)
21
17
  end
22
18
  end
23
19
  end
@@ -0,0 +1,107 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Naming
6
+ # In Ruby 3.1, anonymous block forwarding has been added.
7
+ #
8
+ # This cop identifies places where `do_something(&block)` can be replaced
9
+ # by `do_something(&)`.
10
+ #
11
+ # It also supports the opposite style by alternative `explicit` option.
12
+ #
13
+ # @example EnforcedStyle: anonymous (default)
14
+ #
15
+ # # bad
16
+ # def foo(&block)
17
+ # bar(&block)
18
+ # end
19
+ #
20
+ # # good
21
+ # def foo(&)
22
+ # bar(&)
23
+ # end
24
+ #
25
+ # @example EnforcedStyle: explicit
26
+ #
27
+ # # bad
28
+ # def foo(&)
29
+ # bar(&)
30
+ # end
31
+ #
32
+ # # good
33
+ # def foo(&block)
34
+ # bar(&block)
35
+ # end
36
+ #
37
+ class BlockForwarding < Base
38
+ include ConfigurableEnforcedStyle
39
+ include RangeHelp
40
+ extend AutoCorrector
41
+ extend TargetRubyVersion
42
+
43
+ minimum_target_ruby_version 3.1
44
+
45
+ MSG = 'Use %<style>s block forwarding.'
46
+
47
+ def on_def(node)
48
+ return if node.arguments.empty?
49
+
50
+ last_argument = node.arguments.last
51
+ return if expected_block_forwarding_style?(node, last_argument)
52
+
53
+ register_offense(last_argument)
54
+
55
+ node.each_descendant(:block_pass) do |block_pass_node|
56
+ next if block_pass_node.children.first&.sym_type?
57
+
58
+ register_offense(block_pass_node)
59
+ end
60
+ end
61
+ alias on_defs on_def
62
+
63
+ private
64
+
65
+ def expected_block_forwarding_style?(node, last_argument)
66
+ if style == :anonymous
67
+ !explicit_block_argument?(last_argument) ||
68
+ use_kwarg_in_method_definition?(node) ||
69
+ use_block_argument_as_local_variable?(node, last_argument)
70
+ else
71
+ !anonymous_block_argument?(last_argument)
72
+ end
73
+ end
74
+
75
+ def use_kwarg_in_method_definition?(node)
76
+ node.arguments.each_descendant(:kwarg, :kwoptarg).any?
77
+ end
78
+
79
+ def anonymous_block_argument?(node)
80
+ node.blockarg_type? && node.name.nil?
81
+ end
82
+
83
+ def explicit_block_argument?(node)
84
+ node.blockarg_type? && !node.name.nil?
85
+ end
86
+
87
+ def use_block_argument_as_local_variable?(node, last_argument)
88
+ return if node.body.nil?
89
+
90
+ node.body.each_descendant(:lvar).any? do |lvar|
91
+ !lvar.parent.block_pass_type? && lvar.source == last_argument.source[1..-1]
92
+ end
93
+ end
94
+
95
+ def register_offense(block_argument)
96
+ add_offense(block_argument, message: format(MSG, style: style)) do |corrector|
97
+ corrector.replace(block_argument, '&')
98
+
99
+ arguments = block_argument.parent
100
+
101
+ add_parentheses(arguments, corrector) unless arguments.parenthesized_call?
102
+ end
103
+ end
104
+ end
105
+ end
106
+ end
107
+ end
@@ -14,6 +14,18 @@ module RuboCop
14
14
  # (i.e. `bundler-console` becomes `Bundler::Console`). As such, the
15
15
  # gemspec is supposed to be named `bundler-console.gemspec`.
16
16
  #
17
+ # When `ExpectMatchingDefinition` (default: `false`) is `true`, the cop requires
18
+ # each file to have a class, module or `Struct` defined in it that matches
19
+ # the filename. This can be further configured using
20
+ # `CheckDefinitionPathHierarchy` (default: `true`) to determine whether the
21
+ # path should match the namespace of the above definition.
22
+ #
23
+ # When `IgnoreExecutableScripts` (default: `true`) is `true`, files that start
24
+ # with a shebang line are not considered by the cop.
25
+ #
26
+ # When `Regex` is set, the cop will flag any filename that does not match
27
+ # the regular expression.
28
+ #
17
29
  # @example
18
30
  # # bad
19
31
  # lib/layoutManager.rb
@@ -28,11 +40,19 @@ module RuboCop
28
40
  include RangeHelp
29
41
 
30
42
  MSG_SNAKE_CASE = 'The name of this source file (`%<basename>s`) should use snake_case.'
31
- MSG_NO_DEFINITION = '%<basename>s should define a class or module called `%<namespace>s`.'
43
+ MSG_NO_DEFINITION = '`%<basename>s` should define a class or module called `%<namespace>s`.'
32
44
  MSG_REGEX = '`%<basename>s` should match `%<regex>s`.'
33
45
 
34
46
  SNAKE_CASE = /^[\d[[:lower:]]_.?!]+$/.freeze
35
47
 
48
+ # @!method struct_definition(node)
49
+ def_node_matcher :struct_definition, <<~PATTERN
50
+ {
51
+ (casgn $_ $_ (send (const {nil? cbase} :Struct) :new ...))
52
+ (casgn $_ $_ (block (send (const {nil? cbase} :Struct) :new ...) ...))
53
+ }
54
+ PATTERN
55
+
36
56
  def on_new_investigation
37
57
  file_path = processed_source.file_path
38
58
  return if config.file_to_exclude?(file_path) || config.allowed_camel_case_file?(file_path)
@@ -103,6 +123,10 @@ module RuboCop
103
123
  cop_config['CheckDefinitionPathHierarchy']
104
124
  end
105
125
 
126
+ def definition_path_hierarchy_roots
127
+ cop_config['CheckDefinitionPathHierarchyRoots'] || []
128
+ end
129
+
106
130
  def regex
107
131
  cop_config['Regex']
108
132
  end
@@ -126,7 +150,7 @@ module RuboCop
126
150
  name = namespace.pop
127
151
 
128
152
  on_node(%i[class module casgn], node) do |child|
129
- next unless (const = child.defined_module)
153
+ next unless (const = find_definition(child))
130
154
 
131
155
  const_namespace, const_name = *const
132
156
  next if name != const_name && !match_acronym?(name, const_name)
@@ -138,6 +162,15 @@ module RuboCop
138
162
  nil
139
163
  end
140
164
 
165
+ def find_definition(node)
166
+ node.defined_module || defined_struct(node)
167
+ end
168
+
169
+ def defined_struct(node)
170
+ namespace, name = *struct_definition(node)
171
+ s(:const, namespace, name) if name
172
+ end
173
+
141
174
  def match_namespace(node, namespace, expected)
142
175
  match_partial = partial_matcher!(expected)
143
176
 
@@ -177,13 +210,13 @@ module RuboCop
177
210
  allowed_acronyms.any? { |acronym| expected.gsub(acronym.capitalize, acronym) == name }
178
211
  end
179
212
 
180
- def to_namespace(path)
213
+ def to_namespace(path) # rubocop:disable Metrics/AbcSize
181
214
  components = Pathname(path).each_filename.to_a
182
215
  # To convert a pathname to a Ruby namespace, we need a starting point
183
216
  # But RC can be run from any working directory, and can check any path
184
217
  # We can't assume that the working directory, or any other, is the
185
218
  # "starting point" to build a namespace.
186
- start = %w[lib spec test src]
219
+ start = definition_path_hierarchy_roots
187
220
  start_index = nil
188
221
 
189
222
  # To find the closest namespace root take the path components, and
@@ -3,7 +3,8 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Security
6
- # This cop checks for the use of `Kernel#open` and `URI.open`.
6
+ # This cop checks for the use of `Kernel#open` and `URI.open` with dynamic
7
+ # data.
7
8
  #
8
9
  # `Kernel#open` and `URI.open` enable not only file access but also process
9
10
  # invocation by prefixing a pipe symbol (e.g., `open("| ls")`).
@@ -11,6 +12,9 @@ module RuboCop
11
12
  # the argument of `Kernel#open` and `URI.open`. It would be better to use
12
13
  # `File.open`, `IO.popen` or `URI.parse#open` explicitly.
13
14
  #
15
+ # NOTE: `open` and `URI.open` with literal strings are not flagged by this
16
+ # cop.
17
+ #
14
18
  # @safety
15
19
  # This cop could register false positives if `open` is redefined
16
20
  # in a class and then used without a receiver in that class.
@@ -18,12 +22,18 @@ module RuboCop
18
22
  # @example
19
23
  # # bad
20
24
  # open(something)
25
+ # open("| #{something}")
21
26
  # URI.open(something)
22
27
  #
23
28
  # # good
24
29
  # File.open(something)
25
30
  # IO.popen(something)
26
31
  # URI.parse(something).open
32
+ #
33
+ # # good (literal strings)
34
+ # open("foo.text")
35
+ # open("| foo")
36
+ # URI.open("http://example.com")
27
37
  class Open < Base
28
38
  MSG = 'The use of `%<receiver>sopen` is a serious security risk.'
29
39
  RESTRICT_ON_SEND = %i[open].freeze
@@ -4,6 +4,12 @@ module RuboCop
4
4
  module Cop
5
5
  module Style
6
6
  # Checks for uses of the character literal ?x.
7
+ # Starting with Ruby 1.9 character literals are
8
+ # essentially one-character strings, so this syntax
9
+ # is mostly redundant at this point.
10
+ #
11
+ # ? character literal can be used to express meta and control character.
12
+ # That's a good use case of ? literal so it doesn't count it as an offense.
7
13
  #
8
14
  # @example
9
15
  # # bad
@@ -12,8 +18,9 @@ module RuboCop
12
18
  # # good
13
19
  # 'x'
14
20
  #
15
- # # good
21
+ # # good - control & meta escapes
16
22
  # ?\C-\M-d
23
+ # "\C-\M-d" # same as above
17
24
  class CharacterLiteral < Base
18
25
  include StringHelp
19
26
  extend AutoCorrector
@@ -16,6 +16,7 @@ module RuboCop
16
16
  #
17
17
  # @example
18
18
  # # bad
19
+ # array.reject(&:nil?)
19
20
  # array.reject { |e| e.nil? }
20
21
  # array.select { |e| !e.nil? }
21
22
  #
@@ -23,6 +24,7 @@ module RuboCop
23
24
  # array.compact
24
25
  #
25
26
  # # bad
27
+ # hash.reject!(&:nil?)
26
28
  # hash.reject! { |k, v| v.nil? }
27
29
  # hash.select! { |k, v| !v.nil? }
28
30
  #
@@ -37,11 +39,18 @@ module RuboCop
37
39
 
38
40
  RESTRICT_ON_SEND = %i[reject reject! select select!].freeze
39
41
 
42
+ # @!method reject_method_with_block_pass?(node)
43
+ def_node_matcher :reject_method_with_block_pass?, <<~PATTERN
44
+ (send _ {:reject :reject!}
45
+ (block_pass
46
+ (sym :nil?)))
47
+ PATTERN
48
+
40
49
  # @!method reject_method?(node)
41
50
  def_node_matcher :reject_method?, <<~PATTERN
42
51
  (block
43
52
  (send
44
- _ ${:reject :reject!})
53
+ _ {:reject :reject!})
45
54
  $(args ...)
46
55
  (send
47
56
  $(lvar _) :nil?))
@@ -51,7 +60,7 @@ module RuboCop
51
60
  def_node_matcher :select_method?, <<~PATTERN
52
61
  (block
53
62
  (send
54
- _ ${:select :select!})
63
+ _ {:select :select!})
55
64
  $(args ...)
56
65
  (send
57
66
  (send
@@ -59,16 +68,9 @@ module RuboCop
59
68
  PATTERN
60
69
 
61
70
  def on_send(node)
62
- block_node = node.parent
63
- return unless block_node&.block_type?
64
-
65
- return unless (method_name, args, receiver =
66
- reject_method?(block_node) || select_method?(block_node))
71
+ return unless (range = offense_range(node))
67
72
 
68
- return unless args.last.source == receiver.source
69
-
70
- range = offense_range(node, block_node)
71
- good = good_method_name(method_name)
73
+ good = good_method_name(node.method_name)
72
74
  message = format(MSG, good: good, bad: range.source)
73
75
 
74
76
  add_offense(range, message: message) { |corrector| corrector.replace(range, good) }
@@ -76,6 +78,22 @@ module RuboCop
76
78
 
77
79
  private
78
80
 
81
+ def offense_range(node)
82
+ if reject_method_with_block_pass?(node)
83
+ range(node, node)
84
+ else
85
+ block_node = node.parent
86
+
87
+ return unless block_node&.block_type?
88
+ unless (args, receiver = reject_method?(block_node) || select_method?(block_node))
89
+ return
90
+ end
91
+ return unless args.last.source == receiver.source
92
+
93
+ range(node, block_node)
94
+ end
95
+ end
96
+
79
97
  def good_method_name(method_name)
80
98
  if method_name.to_s.end_with?('!')
81
99
  'compact!'
@@ -84,8 +102,8 @@ module RuboCop
84
102
  end
85
103
  end
86
104
 
87
- def offense_range(send_node, block_node)
88
- range_between(send_node.loc.selector.begin_pos, block_node.loc.end.end_pos)
105
+ def range(begin_pos_node, end_pos_node)
106
+ range_between(begin_pos_node.loc.selector.begin_pos, end_pos_node.loc.end.end_pos)
89
107
  end
90
108
  end
91
109
  end
@@ -77,14 +77,14 @@ module RuboCop
77
77
 
78
78
  def collection_looping_method?(node)
79
79
  # TODO: Remove `Symbol#to_s` after supporting only Ruby >= 2.7.
80
- method_name = node.send_node.method_name.to_s
80
+ method_name = node.method_name.to_s
81
81
  method_name.start_with?('each') || method_name.end_with?('_each')
82
82
  end
83
83
 
84
84
  def same_collection_looping?(node, sibling)
85
85
  sibling&.block_type? &&
86
86
  sibling.send_node.method?(node.method_name) &&
87
- sibling.send_node.receiver == node.send_node.receiver &&
87
+ sibling.receiver == node.receiver &&
88
88
  sibling.send_node.arguments == node.send_node.arguments
89
89
  end
90
90
  end
@@ -176,7 +176,7 @@ module RuboCop
176
176
  end
177
177
 
178
178
  def qualify_const(node)
179
- return if node.nil?
179
+ return if node.nil? || node.cbase_type?
180
180
 
181
181
  [qualify_const(node.namespace), node.short_name].compact
182
182
  end
@@ -79,6 +79,8 @@ module RuboCop
79
79
  when_nodes.each do |when_node|
80
80
  conditions = when_node.conditions
81
81
 
82
+ replace_then_with_line_break(corrector, conditions, when_node)
83
+
82
84
  next unless conditions.size > 1
83
85
 
84
86
  range = range_between(conditions.first.source_range.begin_pos,
@@ -97,6 +99,14 @@ module RuboCop
97
99
  line_beginning = case_range.adjust(begin_pos: -case_range.column)
98
100
  corrector.insert_before(line_beginning, comments)
99
101
  end
102
+
103
+ def replace_then_with_line_break(corrector, conditions, when_node)
104
+ return unless when_node.parent.parent && when_node.then?
105
+
106
+ range = range_between(conditions.last.source_range.end_pos, when_node.loc.begin.end_pos)
107
+
108
+ corrector.replace(range, "\n")
109
+ end
100
110
  end
101
111
  end
102
112
  end
@@ -9,7 +9,7 @@ module RuboCop
9
9
  # to go on its own line (expanded style).
10
10
  #
11
11
  # NOTE: A method definition is not considered empty if it contains
12
- # comments.
12
+ # comments.
13
13
  #
14
14
  # @example EnforcedStyle: compact (default)
15
15
  # # bad