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.
- checksums.yaml +4 -4
- data/README.md +20 -14
- data/config/default.yml +72 -7
- data/config/obsoletion.yml +6 -3
- data/lib/rubocop/cop/autocorrect_logic.rb +18 -10
- data/lib/rubocop/cop/bundler/ordered_gems.rb +1 -1
- data/lib/rubocop/cop/correctors/parentheses_corrector.rb +5 -2
- data/lib/rubocop/cop/gemspec/attribute_assignment.rb +91 -0
- data/lib/rubocop/cop/gemspec/duplicated_assignment.rb +0 -22
- data/lib/rubocop/cop/gemspec/ordered_dependencies.rb +1 -1
- data/lib/rubocop/cop/gemspec/require_mfa.rb +15 -1
- data/lib/rubocop/cop/internal_affairs/node_matcher_directive.rb +4 -4
- data/lib/rubocop/cop/internal_affairs/node_pattern_groups.rb +1 -0
- data/lib/rubocop/cop/layout/class_structure.rb +35 -0
- data/lib/rubocop/cop/layout/closing_parenthesis_indentation.rb +1 -1
- data/lib/rubocop/cop/layout/empty_lines_around_access_modifier.rb +7 -3
- data/lib/rubocop/cop/layout/first_argument_indentation.rb +1 -1
- data/lib/rubocop/cop/layout/line_length.rb +26 -5
- data/lib/rubocop/cop/layout/space_before_brackets.rb +2 -9
- data/lib/rubocop/cop/layout/space_inside_array_literal_brackets.rb +7 -2
- data/lib/rubocop/cop/lint/ambiguous_range.rb +5 -0
- data/lib/rubocop/cop/lint/duplicate_methods.rb +1 -1
- data/lib/rubocop/cop/lint/empty_interpolation.rb +3 -1
- data/lib/rubocop/cop/lint/float_comparison.rb +31 -4
- data/lib/rubocop/cop/lint/identity_comparison.rb +19 -15
- data/lib/rubocop/cop/lint/literal_as_condition.rb +19 -27
- data/lib/rubocop/cop/lint/redundant_regexp_quantifiers.rb +1 -1
- data/lib/rubocop/cop/lint/safe_navigation_chain.rb +4 -4
- data/lib/rubocop/cop/lint/self_assignment.rb +25 -0
- data/lib/rubocop/cop/lint/shadowing_outer_local_variable.rb +5 -0
- data/lib/rubocop/cop/lint/useless_access_modifier.rb +29 -4
- data/lib/rubocop/cop/lint/useless_default_value_argument.rb +90 -0
- data/lib/rubocop/cop/lint/useless_or.rb +98 -0
- data/lib/rubocop/cop/lint/useless_ruby2_keywords.rb +3 -3
- data/lib/rubocop/cop/mixin/alignment.rb +1 -1
- data/lib/rubocop/cop/mixin/frozen_string_literal.rb +1 -1
- data/lib/rubocop/cop/mixin/gemspec_help.rb +22 -0
- data/lib/rubocop/cop/mixin/line_length_help.rb +24 -8
- data/lib/rubocop/cop/mixin/ordered_gem_node.rb +1 -1
- data/lib/rubocop/cop/naming/file_name.rb +2 -2
- data/lib/rubocop/cop/naming/predicate_method.rb +281 -0
- data/lib/rubocop/cop/naming/{predicate_name.rb → predicate_prefix.rb} +4 -4
- data/lib/rubocop/cop/style/case_like_if.rb +1 -1
- data/lib/rubocop/cop/style/collection_querying.rb +167 -0
- data/lib/rubocop/cop/style/conditional_assignment.rb +3 -1
- data/lib/rubocop/cop/style/def_with_parentheses.rb +18 -5
- data/lib/rubocop/cop/style/empty_string_inside_interpolation.rb +100 -0
- data/lib/rubocop/cop/style/exponential_notation.rb +2 -2
- data/lib/rubocop/cop/style/fetch_env_var.rb +32 -6
- data/lib/rubocop/cop/style/hash_conversion.rb +12 -3
- data/lib/rubocop/cop/style/if_unless_modifier.rb +13 -6
- data/lib/rubocop/cop/style/it_block_parameter.rb +33 -14
- data/lib/rubocop/cop/style/map_to_hash.rb +11 -0
- data/lib/rubocop/cop/style/method_call_with_args_parentheses/omit_parentheses.rb +2 -2
- data/lib/rubocop/cop/style/min_max_comparison.rb +13 -5
- data/lib/rubocop/cop/style/redundant_array_flatten.rb +50 -0
- data/lib/rubocop/cop/style/redundant_format.rb +6 -1
- data/lib/rubocop/cop/style/redundant_interpolation.rb +1 -1
- data/lib/rubocop/cop/style/redundant_parentheses.rb +26 -5
- data/lib/rubocop/cop/style/redundant_self.rb +8 -5
- data/lib/rubocop/cop/style/safe_navigation.rb +24 -11
- data/lib/rubocop/cop/style/sole_nested_conditional.rb +2 -1
- data/lib/rubocop/cop/style/symbol_proc.rb +1 -1
- data/lib/rubocop/cop/style/trailing_comma_in_block_args.rb +1 -1
- data/lib/rubocop/cop/team.rb +1 -1
- data/lib/rubocop/formatter/fuubar_style_formatter.rb +1 -1
- data/lib/rubocop/formatter/offense_count_formatter.rb +1 -1
- data/lib/rubocop/lsp/diagnostic.rb +4 -4
- data/lib/rubocop/rspec/expect_offense.rb +9 -3
- data/lib/rubocop/version.rb +1 -1
- data/lib/rubocop.rb +8 -1
- data/lib/ruby_lsp/rubocop/addon.rb +2 -2
- 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
|
29
|
-
|
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
|
37
|
-
|
38
|
-
return nil unless
|
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 =
|
41
|
-
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
|
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? ||
|
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
|
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
|
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? #
|
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/
|
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
|
198
|
+
def method_definition_macro?(macro_name)
|
199
199
|
cop_config['MethodDefinitionMacros'].include?(macro_name.to_s)
|
200
200
|
end
|
201
201
|
end
|