rubocop 1.75.7 → 1.77.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 (73) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +20 -14
  3. data/config/default.yml +72 -7
  4. data/config/obsoletion.yml +6 -3
  5. data/lib/rubocop/cop/autocorrect_logic.rb +18 -10
  6. data/lib/rubocop/cop/bundler/ordered_gems.rb +1 -1
  7. data/lib/rubocop/cop/correctors/parentheses_corrector.rb +5 -2
  8. data/lib/rubocop/cop/gemspec/attribute_assignment.rb +91 -0
  9. data/lib/rubocop/cop/gemspec/duplicated_assignment.rb +0 -22
  10. data/lib/rubocop/cop/gemspec/ordered_dependencies.rb +1 -1
  11. data/lib/rubocop/cop/gemspec/require_mfa.rb +15 -1
  12. data/lib/rubocop/cop/internal_affairs/node_matcher_directive.rb +4 -4
  13. data/lib/rubocop/cop/internal_affairs/node_pattern_groups.rb +1 -0
  14. data/lib/rubocop/cop/layout/class_structure.rb +35 -0
  15. data/lib/rubocop/cop/layout/closing_parenthesis_indentation.rb +1 -1
  16. data/lib/rubocop/cop/layout/empty_lines_around_access_modifier.rb +7 -3
  17. data/lib/rubocop/cop/layout/first_argument_indentation.rb +1 -1
  18. data/lib/rubocop/cop/layout/line_length.rb +26 -5
  19. data/lib/rubocop/cop/layout/space_before_brackets.rb +2 -9
  20. data/lib/rubocop/cop/layout/space_inside_array_literal_brackets.rb +7 -2
  21. data/lib/rubocop/cop/lint/ambiguous_range.rb +5 -0
  22. data/lib/rubocop/cop/lint/duplicate_methods.rb +1 -1
  23. data/lib/rubocop/cop/lint/empty_interpolation.rb +3 -1
  24. data/lib/rubocop/cop/lint/float_comparison.rb +31 -4
  25. data/lib/rubocop/cop/lint/identity_comparison.rb +19 -15
  26. data/lib/rubocop/cop/lint/literal_as_condition.rb +19 -27
  27. data/lib/rubocop/cop/lint/redundant_regexp_quantifiers.rb +1 -1
  28. data/lib/rubocop/cop/lint/safe_navigation_chain.rb +4 -4
  29. data/lib/rubocop/cop/lint/self_assignment.rb +25 -0
  30. data/lib/rubocop/cop/lint/shadowing_outer_local_variable.rb +5 -0
  31. data/lib/rubocop/cop/lint/useless_access_modifier.rb +29 -4
  32. data/lib/rubocop/cop/lint/useless_default_value_argument.rb +90 -0
  33. data/lib/rubocop/cop/lint/useless_or.rb +98 -0
  34. data/lib/rubocop/cop/lint/useless_ruby2_keywords.rb +3 -3
  35. data/lib/rubocop/cop/mixin/alignment.rb +1 -1
  36. data/lib/rubocop/cop/mixin/frozen_string_literal.rb +1 -1
  37. data/lib/rubocop/cop/mixin/gemspec_help.rb +22 -0
  38. data/lib/rubocop/cop/mixin/line_length_help.rb +24 -8
  39. data/lib/rubocop/cop/mixin/ordered_gem_node.rb +1 -1
  40. data/lib/rubocop/cop/naming/file_name.rb +2 -2
  41. data/lib/rubocop/cop/naming/predicate_method.rb +281 -0
  42. data/lib/rubocop/cop/naming/{predicate_name.rb → predicate_prefix.rb} +4 -4
  43. data/lib/rubocop/cop/style/case_like_if.rb +1 -1
  44. data/lib/rubocop/cop/style/collection_querying.rb +167 -0
  45. data/lib/rubocop/cop/style/conditional_assignment.rb +3 -1
  46. data/lib/rubocop/cop/style/def_with_parentheses.rb +18 -5
  47. data/lib/rubocop/cop/style/empty_string_inside_interpolation.rb +100 -0
  48. data/lib/rubocop/cop/style/exponential_notation.rb +2 -2
  49. data/lib/rubocop/cop/style/fetch_env_var.rb +32 -6
  50. data/lib/rubocop/cop/style/hash_conversion.rb +12 -3
  51. data/lib/rubocop/cop/style/if_unless_modifier.rb +13 -6
  52. data/lib/rubocop/cop/style/it_block_parameter.rb +33 -14
  53. data/lib/rubocop/cop/style/map_to_hash.rb +11 -0
  54. data/lib/rubocop/cop/style/method_call_with_args_parentheses/omit_parentheses.rb +2 -2
  55. data/lib/rubocop/cop/style/min_max_comparison.rb +13 -5
  56. data/lib/rubocop/cop/style/redundant_array_flatten.rb +50 -0
  57. data/lib/rubocop/cop/style/redundant_format.rb +6 -1
  58. data/lib/rubocop/cop/style/redundant_interpolation.rb +1 -1
  59. data/lib/rubocop/cop/style/redundant_parentheses.rb +26 -5
  60. data/lib/rubocop/cop/style/redundant_self.rb +8 -5
  61. data/lib/rubocop/cop/style/safe_navigation.rb +24 -11
  62. data/lib/rubocop/cop/style/sole_nested_conditional.rb +2 -1
  63. data/lib/rubocop/cop/style/symbol_proc.rb +1 -1
  64. data/lib/rubocop/cop/style/trailing_comma_in_block_args.rb +1 -1
  65. data/lib/rubocop/cop/team.rb +1 -1
  66. data/lib/rubocop/formatter/fuubar_style_formatter.rb +1 -1
  67. data/lib/rubocop/formatter/offense_count_formatter.rb +1 -1
  68. data/lib/rubocop/lsp/diagnostic.rb +4 -4
  69. data/lib/rubocop/rspec/expect_offense.rb +9 -3
  70. data/lib/rubocop/version.rb +1 -1
  71. data/lib/rubocop.rb +8 -1
  72. data/lib/ruby_lsp/rubocop/addon.rb +2 -2
  73. metadata +14 -7
@@ -0,0 +1,90 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Lint
6
+ # Checks for usage of method `fetch` or `Array.new` with default value argument
7
+ # and block. In such cases, block will always be used as default value.
8
+ #
9
+ # This cop emulates Ruby warning "block supersedes default value argument" which
10
+ # applies to `Array.new`, `Array#fetch`, `Hash#fetch`, `ENV.fetch` and
11
+ # `Thread#fetch`.
12
+ #
13
+ # A `fetch` call without a receiver is considered a custom method and does not register
14
+ # an offense.
15
+ #
16
+ # @safety
17
+ # This cop is unsafe because the receiver could have nonstandard implementation
18
+ # of `fetch`, or be a class other than the one listed above.
19
+ #
20
+ # It is also unsafe because default value argument could have side effects:
21
+ #
22
+ # [source,ruby]
23
+ # ----
24
+ # def x(a) = puts "side effect"
25
+ # Array.new(5, x(1)) { 2 }
26
+ # ----
27
+ #
28
+ # so removing it would change behavior.
29
+ #
30
+ # @example
31
+ # # bad
32
+ # x.fetch(key, default_value) { block_value }
33
+ # Array.new(size, default_value) { block_value }
34
+ #
35
+ # # good
36
+ # x.fetch(key) { block_value }
37
+ # Array.new(size) { block_value }
38
+ #
39
+ # # also good - in case default value argument is desired instead
40
+ # x.fetch(key, default_value)
41
+ # Array.new(size, default_value)
42
+ #
43
+ # # good - keyword arguments aren't registered as offenses
44
+ # x.fetch(key, keyword: :arg) { block_value }
45
+ #
46
+ # @example AllowedReceivers: ['Rails.cache']
47
+ # # good
48
+ # Rails.cache.fetch(name, options) { block }
49
+ #
50
+ class UselessDefaultValueArgument < Base
51
+ include AllowedReceivers
52
+ extend AutoCorrector
53
+
54
+ MSG = 'Block supersedes default value argument.'
55
+
56
+ RESTRICT_ON_SEND = %i[fetch new].freeze
57
+
58
+ # @!method default_value_argument_and_block(node)
59
+ def_node_matcher :default_value_argument_and_block, <<~PATTERN
60
+ (any_block
61
+ {
62
+ (call !nil? :fetch $_key $_default_value)
63
+ (send (const _ :Array) :new $_size $_default_value)
64
+ }
65
+ _args
66
+ _block_body)
67
+ PATTERN
68
+
69
+ def on_send(node)
70
+ unless (prev_arg_node, default_value_node = default_value_argument_and_block(node.parent))
71
+ return
72
+ end
73
+ return if allowed_receiver?(node.receiver)
74
+ return if hash_without_braces?(default_value_node)
75
+
76
+ add_offense(default_value_node) do |corrector|
77
+ corrector.remove(prev_arg_node.source_range.end.join(default_value_node.source_range))
78
+ end
79
+ end
80
+ alias on_csend on_send
81
+
82
+ private
83
+
84
+ def hash_without_braces?(node)
85
+ node.hash_type? && !node.braces?
86
+ end
87
+ end
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,98 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Lint
6
+ # Checks for useless OR (`||` and `or`) expressions.
7
+ #
8
+ # Some methods always return a truthy value, even when called
9
+ # on `nil` (e.g. `nil.to_i` evaluates to `0`). Therefore, OR expressions
10
+ # appended after these methods will never evaluate.
11
+ #
12
+ # @example
13
+ #
14
+ # # bad
15
+ # x.to_a || fallback
16
+ # x.to_c || fallback
17
+ # x.to_d || fallback
18
+ # x.to_i || fallback
19
+ # x.to_f || fallback
20
+ # x.to_h || fallback
21
+ # x.to_r || fallback
22
+ # x.to_s || fallback
23
+ # x.to_sym || fallback
24
+ # x.intern || fallback
25
+ # x.inspect || fallback
26
+ # x.hash || fallback
27
+ # x.object_id || fallback
28
+ # x.__id__ || fallback
29
+ #
30
+ # x.to_s or fallback
31
+ #
32
+ # # good - if fallback is same as return value of method called on nil
33
+ # x.to_a # nil.to_a returns []
34
+ # x.to_c # nil.to_c returns (0+0i)
35
+ # x.to_d # nil.to_d returns 0.0
36
+ # x.to_i # nil.to_i returns 0
37
+ # x.to_f # nil.to_f returns 0.0
38
+ # x.to_h # nil.to_h returns {}
39
+ # x.to_r # nil.to_r returns (0/1)
40
+ # x.to_s # nil.to_s returns ''
41
+ # x.to_sym # nil.to_sym raises an error
42
+ # x.intern # nil.intern raises an error
43
+ # x.inspect # nil.inspect returns "nil"
44
+ # x.hash # nil.hash returns an Integer
45
+ # x.object_id # nil.object_id returns an Integer
46
+ # x.__id__ # nil.object_id returns an Integer
47
+ #
48
+ # # good - if the intention is not to call the method on nil
49
+ # x&.to_a || fallback
50
+ # x&.to_c || fallback
51
+ # x&.to_d || fallback
52
+ # x&.to_i || fallback
53
+ # x&.to_f || fallback
54
+ # x&.to_h || fallback
55
+ # x&.to_r || fallback
56
+ # x&.to_s || fallback
57
+ # x&.to_sym || fallback
58
+ # x&.intern || fallback
59
+ # x&.inspect || fallback
60
+ # x&.hash || fallback
61
+ # x&.object_id || fallback
62
+ # x&.__id__ || fallback
63
+ #
64
+ # x&.to_s or fallback
65
+ #
66
+ class UselessOr < Base
67
+ MSG = '`%<rhs>s` will never evaluate because `%<lhs>s` always returns a truthy value.'
68
+
69
+ TRUTHY_RETURN_VALUE_METHODS = Set[:to_a, :to_c, :to_d, :to_i, :to_f, :to_h, :to_r,
70
+ :to_s, :to_sym, :intern, :inspect, :hash, :object_id,
71
+ :__id__].freeze
72
+
73
+ # @!method truthy_return_value_method?(node)
74
+ def_node_matcher :truthy_return_value_method?, <<~PATTERN
75
+ (send _ %TRUTHY_RETURN_VALUE_METHODS)
76
+ PATTERN
77
+
78
+ def on_or(node)
79
+ if truthy_return_value_method?(node.lhs)
80
+ report_offense(node, node.lhs)
81
+ elsif truthy_return_value_method?(node.rhs)
82
+ parent = node.parent
83
+ parent = parent.parent if parent&.begin_type?
84
+
85
+ report_offense(parent, node.rhs) if parent&.or_type?
86
+ end
87
+ end
88
+
89
+ private
90
+
91
+ def report_offense(or_node, truthy_node)
92
+ add_offense(or_node.loc.operator.join(or_node.rhs.source_range),
93
+ message: format(MSG, lhs: truthy_node.source, rhs: or_node.rhs.source))
94
+ end
95
+ end
96
+ end
97
+ end
98
+ end
@@ -89,7 +89,7 @@ module RuboCop
89
89
  private
90
90
 
91
91
  def inspect_def(node, def_node)
92
- return if allowed_arguments(def_node.arguments)
92
+ return if allowed_arguments?(def_node.arguments)
93
93
 
94
94
  add_offense(node.loc.selector, message: format(MSG, method_name: def_node.method_name))
95
95
  end
@@ -101,7 +101,7 @@ module RuboCop
101
101
  definition = find_method_definition(node, method_name)
102
102
 
103
103
  return unless definition
104
- return if allowed_arguments(definition.arguments)
104
+ return if allowed_arguments?(definition.arguments)
105
105
 
106
106
  add_offense(node, message: format(MSG, method_name: method_name))
107
107
  end
@@ -115,7 +115,7 @@ module RuboCop
115
115
  end
116
116
 
117
117
  # `ruby2_keywords` is only allowed if there's a `restarg` and no keyword arguments
118
- def allowed_arguments(arguments)
118
+ def allowed_arguments?(arguments)
119
119
  return false if arguments.empty?
120
120
 
121
121
  arguments.each_child_node(:restarg).any? &&
@@ -66,7 +66,7 @@ module RuboCop
66
66
  end
67
67
 
68
68
  # @deprecated Use processed_source.line_with_comment?(line)
69
- def end_of_line_comment(line)
69
+ def end_of_line_comment(line) # rubocop:disable Naming/PredicateMethod
70
70
  warn Rainbow(<<~WARNING).yellow, uplevel: 1
71
71
  `end_of_line_comment` is deprecated. Use `processed_source.line_with_comment?` instead.
72
72
  WARNING
@@ -89,7 +89,7 @@ module RuboCop
89
89
 
90
90
  if first_non_comment_token
91
91
  # `line` is 1-indexed so we need to subtract 1 to get the array index
92
- processed_source.lines[0...first_non_comment_token.line - 1]
92
+ processed_source.lines[0...(first_non_comment_token.line - 1)]
93
93
  else
94
94
  processed_source.lines
95
95
  end
@@ -25,6 +25,28 @@ module RuboCop
25
25
  (args
26
26
  (arg $_)) ...)
27
27
  PATTERN
28
+
29
+ # @!method assignment_method_declarations(node)
30
+ def_node_search :assignment_method_declarations, <<~PATTERN
31
+ (send
32
+ (lvar {#match_block_variable_name? :_1 :it}) _ ...)
33
+ PATTERN
34
+
35
+ # @!method indexed_assignment_method_declarations(node)
36
+ def_node_search :indexed_assignment_method_declarations, <<~PATTERN
37
+ (send
38
+ (send (lvar {#match_block_variable_name? :_1 :it}) _)
39
+ :[]=
40
+ literal?
41
+ _
42
+ )
43
+ PATTERN
44
+
45
+ def match_block_variable_name?(receiver_name)
46
+ gem_specification(processed_source.ast) do |block_variable_name|
47
+ return block_variable_name == receiver_name
48
+ end
49
+ end
28
50
  end
29
51
  end
30
52
  end
@@ -25,20 +25,24 @@ module RuboCop
25
25
  config.for_cop('Layout/LineLength')['AllowURI']
26
26
  end
27
27
 
28
- def allowed_uri_position?(line, uri_range)
29
- uri_range.begin < max_line_length && uri_range.end == line_length(line)
28
+ def allow_qualified_name?
29
+ config.for_cop('Layout/LineLength')['AllowQualifiedName']
30
+ end
31
+
32
+ def allowed_position?(line, range)
33
+ range.begin < max_line_length && range.end == line_length(line)
30
34
  end
31
35
 
32
36
  def line_length(line)
33
37
  line.length + indentation_difference(line)
34
38
  end
35
39
 
36
- def find_excessive_uri_range(line)
37
- last_uri_match = match_uris(line).last
38
- return nil unless last_uri_match
40
+ def find_excessive_range(line, type)
41
+ last_match = (type == :uri ? match_uris(line) : match_qualified_names(line)).last
42
+ return nil unless last_match
39
43
 
40
- begin_position, end_position = last_uri_match.offset(0)
41
- end_position = extend_uri_end_position(line, end_position)
44
+ begin_position, end_position = last_match.offset(0)
45
+ end_position = extend_end_position(line, end_position)
42
46
 
43
47
  line_indentation_difference = indentation_difference(line)
44
48
  begin_position += line_indentation_difference
@@ -57,6 +61,14 @@ module RuboCop
57
61
  matches
58
62
  end
59
63
 
64
+ def match_qualified_names(string)
65
+ matches = []
66
+ string.scan(qualified_name_regexp) do
67
+ matches << $LAST_MATCH_INFO
68
+ end
69
+ matches
70
+ end
71
+
60
72
  def indentation_difference(line)
61
73
  return 0 unless tab_indentation_width
62
74
 
@@ -70,7 +82,7 @@ module RuboCop
70
82
  index * (tab_indentation_width - 1)
71
83
  end
72
84
 
73
- def extend_uri_end_position(line, end_position)
85
+ def extend_end_position(line, end_position)
74
86
  # Extend the end position YARD comments with linked URLs of the form {<uri> <title>}
75
87
  if line&.match(/{(\s|\S)*}$/)
76
88
  match = line[end_position..line_length(line)]&.match(/(\s|\S)*}/)
@@ -101,6 +113,10 @@ module RuboCop
101
113
  end
102
114
  end
103
115
 
116
+ def qualified_name_regexp
117
+ /\b(?:[A-Z][A-Za-z0-9_]*::)+[A-Za-z_][A-Za-z0-9_]*\b/
118
+ end
119
+
104
120
  def valid_uri?(uri_ish_string)
105
121
  URI.parse(uri_ish_string)
106
122
  true
@@ -24,7 +24,7 @@ module RuboCop
24
24
  gem_canonical_name(string_a) < gem_canonical_name(string_b)
25
25
  end
26
26
 
27
- def consecutive_lines(previous, current)
27
+ def consecutive_lines?(previous, current)
28
28
  first_line = get_source_range(current, treat_comments_as_separators).first_line
29
29
  previous.source_range.last_line == first_line - 1
30
30
  end
@@ -152,7 +152,7 @@ module RuboCop
152
152
 
153
153
  const_namespace, const_name = *const
154
154
  next if name != const_name && !match_acronym?(name, const_name)
155
- next unless namespace.empty? || match_namespace(child, const_namespace, namespace)
155
+ next unless namespace.empty? || namespace_matches?(child, const_namespace, namespace)
156
156
 
157
157
  return node
158
158
  end
@@ -169,7 +169,7 @@ module RuboCop
169
169
  s(:const, namespace, name) if name
170
170
  end
171
171
 
172
- def match_namespace(node, namespace, expected)
172
+ def namespace_matches?(node, namespace, expected)
173
173
  match_partial = partial_matcher!(expected)
174
174
 
175
175
  match_partial.call(namespace)
@@ -0,0 +1,281 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Naming
6
+ # Checks that predicate methods end with `?` and non-predicate methods do not.
7
+ #
8
+ # The names of predicate methods (methods that return a boolean value) should end
9
+ # in a question mark. Methods that don't return a boolean, shouldn't
10
+ # end in a question mark.
11
+ #
12
+ # The cop assesses a predicate method as one that returns boolean values. Likewise,
13
+ # a method that only returns literal values is assessed as non-predicate. Other predicate
14
+ # method calls are assumed to return boolean values. The cop does not make an assessment
15
+ # if the return type is unknown (non-predicate method calls, variables, etc.).
16
+ #
17
+ # NOTE: Operator methods (`def ==`, etc.) are ignored.
18
+ #
19
+ # By default, the cop runs in `conservative` mode, which allows a method to be named
20
+ # with a question mark as long as at least one return value is boolean. In `aggressive`
21
+ # mode, methods with a question mark will register an offense if any known non-boolean
22
+ # return values are detected.
23
+ #
24
+ # The cop also has `AllowedMethods` configuration in order to prevent the cop from
25
+ # registering an offense from a method name that does not confirm to the naming
26
+ # guidelines. By default, `call` is allowed. The cop also has `AllowedPatterns`
27
+ # configuration to allow method names by regular expression.
28
+ #
29
+ # The cop can furthermore be configured to allow all bang methods (method names
30
+ # ending with `!`), with `AllowBangMethods: true` (default false).
31
+ #
32
+ # @example Mode: conservative (default)
33
+ # # bad
34
+ # def foo
35
+ # bar == baz
36
+ # end
37
+ #
38
+ # # good
39
+ # def foo?
40
+ # bar == baz
41
+ # end
42
+ #
43
+ # # bad
44
+ # def foo?
45
+ # 5
46
+ # end
47
+ #
48
+ # # good
49
+ # def foo
50
+ # 5
51
+ # end
52
+ #
53
+ # # bad
54
+ # def foo
55
+ # x == y
56
+ # end
57
+ #
58
+ # # good
59
+ # def foo?
60
+ # x == y
61
+ # end
62
+ #
63
+ # # bad
64
+ # def foo
65
+ # !x
66
+ # end
67
+ #
68
+ # # good
69
+ # def foo?
70
+ # !x
71
+ # end
72
+ #
73
+ # # bad - returns the value of another predicate method
74
+ # def foo
75
+ # bar?
76
+ # end
77
+ #
78
+ # # good
79
+ # def foo?
80
+ # bar?
81
+ # end
82
+ #
83
+ # # good - operator method
84
+ # def ==(other)
85
+ # hash == other.hash
86
+ # end
87
+ #
88
+ # # good - at least one return value is boolean
89
+ # def foo?
90
+ # return unless bar?
91
+ # true
92
+ # end
93
+ #
94
+ # # ok - return type is not known
95
+ # def foo?
96
+ # bar
97
+ # end
98
+ #
99
+ # # ok - return type is not known
100
+ # def foo
101
+ # bar?
102
+ # end
103
+ #
104
+ # @example Mode: aggressive
105
+ # # bad - the method returns nil in some cases
106
+ # def foo?
107
+ # return unless bar?
108
+ # true
109
+ # end
110
+ #
111
+ # @example AllowBangMethods: false (default)
112
+ # # bad
113
+ # def save!
114
+ # true
115
+ # end
116
+ #
117
+ # @example AllowBangMethods: true
118
+ # # good
119
+ # def save!
120
+ # true
121
+ # end
122
+ #
123
+ class PredicateMethod < Base
124
+ include AllowedMethods
125
+ include AllowedPattern
126
+
127
+ MSG_PREDICATE = 'Predicate method names should end with `?`.'
128
+ MSG_NON_PREDICATE = 'Non-predicate method names should not end with `?`.'
129
+
130
+ def on_def(node)
131
+ return if allowed?(node)
132
+
133
+ return_values = return_values(node.body)
134
+ return if acceptable?(return_values)
135
+
136
+ if node.predicate_method? && potential_non_predicate?(return_values)
137
+ add_offense(node.loc.name, message: MSG_NON_PREDICATE)
138
+ elsif !node.predicate_method? && all_return_values_boolean?(return_values)
139
+ add_offense(node.loc.name, message: MSG_PREDICATE)
140
+ end
141
+ end
142
+ alias on_defs on_def
143
+
144
+ private
145
+
146
+ def allowed?(node)
147
+ allowed_method?(node.method_name) ||
148
+ matches_allowed_pattern?(node.method_name) ||
149
+ allowed_bang_method?(node) ||
150
+ node.operator_method? ||
151
+ node.body.nil?
152
+ end
153
+
154
+ def acceptable?(return_values)
155
+ # In `conservative` mode, if the method returns `super`, `zsuper`, or a
156
+ # non-comparison method call, the method name is acceptable.
157
+ return false unless conservative?
158
+
159
+ return_values.any? do |value|
160
+ value.type?(:super, :zsuper) || unknown_method_call?(value)
161
+ end
162
+ end
163
+
164
+ def unknown_method_call?(value)
165
+ return false unless value.call_type?
166
+
167
+ !value.comparison_method? && !value.predicate_method? && !value.negation_method?
168
+ end
169
+
170
+ def return_values(node)
171
+ # Collect all the (implicit and explicit) return values of a node
172
+ return_values = Set.new(node.begin_type? ? [] : [extract_return_value(node)])
173
+
174
+ node.each_descendant(:return) do |return_node|
175
+ return_values << extract_return_value(return_node)
176
+ end
177
+
178
+ last_value = last_value(node)
179
+ return_values << last_value if last_value
180
+
181
+ process_return_values(return_values)
182
+ end
183
+
184
+ def all_return_values_boolean?(return_values)
185
+ values = return_values.reject { |value| value.type?(:super, :zsuper) }
186
+ return false if values.empty?
187
+
188
+ values.all? { |value| boolean_return?(value) }
189
+ end
190
+
191
+ def boolean_return?(value)
192
+ return true if value.boolean_type?
193
+ return false unless value.call_type?
194
+
195
+ value.comparison_method? || value.predicate_method? || value.negation_method?
196
+ end
197
+
198
+ def potential_non_predicate?(return_values)
199
+ # Assumes a method to be non-predicate if all return values are non-boolean literals.
200
+ #
201
+ # In `Mode: conservative`, if any of the return values is a boolean,
202
+ # the method name is acceptable.
203
+ # In `Mode: aggressive`, all return values must be booleans for a predicate
204
+ # method, or else an offense will be registered.
205
+ return false if conservative? && return_values.any? { |value| boolean_return?(value) }
206
+
207
+ return_values.any? do |value|
208
+ value.literal? && !value.boolean_type?
209
+ end
210
+ end
211
+
212
+ def extract_return_value(node)
213
+ return node unless node.return_type?
214
+
215
+ # `return` without a value is a `nil` return.
216
+ return s(:nil) if node.arguments.empty?
217
+
218
+ # When there's a multiple return, it cannot be a predicate
219
+ # so just return an `array` sexp for simplicity.
220
+ return s(:array) unless node.arguments.one?
221
+
222
+ node.first_argument
223
+ end
224
+
225
+ def last_value(node)
226
+ value = node.begin_type? ? node.children.last : node
227
+ value&.return_type? ? extract_return_value(value) : value
228
+ end
229
+
230
+ def process_return_values(return_values)
231
+ return_values.flat_map do |value|
232
+ if value.conditional?
233
+ process_return_values(extract_conditional_branches(value))
234
+ elsif and_or?(value)
235
+ process_return_values(extract_and_or_clauses(value))
236
+ else
237
+ value
238
+ end
239
+ end
240
+ end
241
+
242
+ def and_or?(node)
243
+ node.type?(:and, :or)
244
+ end
245
+
246
+ def extract_and_or_clauses(node)
247
+ # Recursively traverse an `and` or `or` node to collect all clauses within
248
+ return node unless and_or?(node)
249
+
250
+ [extract_and_or_clauses(node.lhs), extract_and_or_clauses(node.rhs)].flatten
251
+ end
252
+
253
+ def extract_conditional_branches(node)
254
+ return node unless node.conditional?
255
+
256
+ if node.type?(:while, :until)
257
+ # If there is no body, act as implicit `nil`.
258
+ node.body ? [last_value(node.body)] : [s(:nil)]
259
+ else
260
+ # Branches with no value act as an implicit `nil`.
261
+ node.branches.filter_map { |branch| branch ? last_value(branch) : s(:nil) }
262
+ end
263
+ end
264
+
265
+ def conservative?
266
+ cop_config.fetch('Mode', :conservative).to_sym == :conservative
267
+ end
268
+
269
+ def allowed_bang_method?(node)
270
+ return false unless allow_bang_methods?
271
+
272
+ node.bang_method?
273
+ end
274
+
275
+ def allow_bang_methods?
276
+ cop_config.fetch('AllowBangMethods', false)
277
+ end
278
+ end
279
+ end
280
+ end
281
+ end
@@ -100,12 +100,12 @@ module RuboCop
100
100
  # # good
101
101
  # def_node_matcher(:even?) { |value| }
102
102
  #
103
- class PredicateName < Base
103
+ class PredicatePrefix < Base
104
104
  include AllowedMethods
105
105
 
106
106
  # @!method dynamic_method_define(node)
107
107
  def_node_matcher :dynamic_method_define, <<~PATTERN
108
- (send nil? #method_definition_macros
108
+ (send nil? #method_definition_macro?
109
109
  (sym $_)
110
110
  ...)
111
111
  PATTERN
@@ -143,7 +143,7 @@ module RuboCop
143
143
  next if predicate_prefixes.include?(forbidden_prefix)
144
144
 
145
145
  raise ValidationError, <<~MSG.chomp
146
- The `Naming/PredicateName` cop is misconfigured. Prefix #{forbidden_prefix} must be included in NamePrefix because it is included in ForbiddenPrefixes.
146
+ The `Naming/PredicatePrefix` cop is misconfigured. Prefix #{forbidden_prefix} must be included in NamePrefix because it is included in ForbiddenPrefixes.
147
147
  MSG
148
148
  end
149
149
  end
@@ -195,7 +195,7 @@ module RuboCop
195
195
  cop_config['UseSorbetSigs']
196
196
  end
197
197
 
198
- def method_definition_macros(macro_name)
198
+ def method_definition_macro?(macro_name)
199
199
  cop_config['MethodDefinitionMacros'].include?(macro_name.to_s)
200
200
  end
201
201
  end
@@ -269,7 +269,7 @@ module RuboCop
269
269
  end
270
270
 
271
271
  def regexp_with_named_captures?(node)
272
- node.regexp_type? && node.each_capture(named: true).count.positive?
272
+ node.regexp_type? && node.each_capture(named: true).any?
273
273
  end
274
274
  end
275
275
  end