rubocop 1.75.6 → 1.76.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 +19 -13
- data/config/default.yml +48 -5
- 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/gemspec/duplicated_assignment.rb +50 -6
- data/lib/rubocop/cop/gemspec/ordered_dependencies.rb +1 -1
- 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/empty_lines_around_access_modifier.rb +6 -2
- data/lib/rubocop/cop/layout/first_argument_indentation.rb +1 -1
- data/lib/rubocop/cop/layout/space_before_brackets.rb +6 -32
- data/lib/rubocop/cop/lint/duplicate_methods.rb +41 -1
- data/lib/rubocop/cop/lint/empty_interpolation.rb +3 -1
- data/lib/rubocop/cop/lint/float_comparison.rb +27 -0
- data/lib/rubocop/cop/lint/identity_comparison.rb +19 -15
- data/lib/rubocop/cop/lint/literal_as_condition.rb +16 -24
- data/lib/rubocop/cop/lint/shadowing_outer_local_variable.rb +5 -0
- data/lib/rubocop/cop/lint/useless_default_value_argument.rb +87 -0
- data/lib/rubocop/cop/lint/useless_or.rb +98 -0
- data/lib/rubocop/cop/metrics/abc_size.rb +1 -1
- data/lib/rubocop/cop/mixin/ordered_gem_node.rb +1 -1
- data/lib/rubocop/cop/naming/predicate_method.rb +216 -0
- data/lib/rubocop/cop/naming/{predicate_name.rb → predicate_prefix.rb} +2 -2
- data/lib/rubocop/cop/style/access_modifier_declarations.rb +32 -10
- data/lib/rubocop/cop/style/command_literal.rb +1 -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/if_unless_modifier.rb +2 -4
- data/lib/rubocop/cop/style/if_unless_modifier_of_if_unless.rb +4 -7
- 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 +1 -1
- data/lib/rubocop/cop/style/redundant_array_flatten.rb +48 -0
- data/lib/rubocop/cop/style/redundant_format.rb +6 -1
- data/lib/rubocop/cop/style/redundant_parentheses.rb +16 -5
- data/lib/rubocop/cop/style/regexp_literal.rb +1 -1
- data/lib/rubocop/cop/style/safe_navigation.rb +10 -7
- data/lib/rubocop/cop/team.rb +1 -1
- data/lib/rubocop/cop/variable_force/assignment.rb +7 -3
- data/lib/rubocop/version.rb +1 -1
- data/lib/rubocop.rb +6 -1
- metadata +12 -7
@@ -15,6 +15,14 @@ module RuboCop
|
|
15
15
|
# x == 0.1
|
16
16
|
# x != 0.1
|
17
17
|
#
|
18
|
+
# # bad
|
19
|
+
# case value
|
20
|
+
# when 1.0
|
21
|
+
# foo
|
22
|
+
# when 2.0
|
23
|
+
# bar
|
24
|
+
# end
|
25
|
+
#
|
18
26
|
# # good - using BigDecimal
|
19
27
|
# x.to_d == 0.1.to_d
|
20
28
|
#
|
@@ -32,12 +40,21 @@ module RuboCop
|
|
32
40
|
# # good - comparing against nil
|
33
41
|
# Float(x, exception: false) == nil
|
34
42
|
#
|
43
|
+
# # good - using epsilon comparison in case expression
|
44
|
+
# case
|
45
|
+
# when (value - 1.0).abs < Float::EPSILON
|
46
|
+
# foo
|
47
|
+
# when (value - 2.0).abs < Float::EPSILON
|
48
|
+
# bar
|
49
|
+
# end
|
50
|
+
#
|
35
51
|
# # Or some other epsilon based type of comparison:
|
36
52
|
# # https://www.embeddeduse.com/2019/08/26/qt-compare-two-floats/
|
37
53
|
#
|
38
54
|
class FloatComparison < Base
|
39
55
|
MSG_EQUALITY = 'Avoid equality comparisons of floats as they are unreliable.'
|
40
56
|
MSG_INEQUALITY = 'Avoid inequality comparisons of floats as they are unreliable.'
|
57
|
+
MSG_CASE = 'Avoid float literal comparisons in case statements as they are unreliable.'
|
41
58
|
|
42
59
|
EQUALITY_METHODS = %i[== != eql? equal?].freeze
|
43
60
|
FLOAT_RETURNING_METHODS = %i[to_f Float fdiv].freeze
|
@@ -58,6 +75,16 @@ module RuboCop
|
|
58
75
|
end
|
59
76
|
alias on_csend on_send
|
60
77
|
|
78
|
+
def on_case(node)
|
79
|
+
node.when_branches.each do |when_branch|
|
80
|
+
when_branch.each_condition do |condition|
|
81
|
+
next if !float?(condition) || literal_safe?(condition)
|
82
|
+
|
83
|
+
add_offense(condition, message: MSG_CASE)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
61
88
|
private
|
62
89
|
|
63
90
|
def float?(node)
|
@@ -11,39 +11,43 @@ module RuboCop
|
|
11
11
|
# @example
|
12
12
|
# # bad
|
13
13
|
# foo.object_id == bar.object_id
|
14
|
+
# foo.object_id != baz.object_id
|
14
15
|
#
|
15
16
|
# # good
|
16
17
|
# foo.equal?(bar)
|
18
|
+
# !foo.equal?(baz)
|
17
19
|
#
|
18
20
|
class IdentityComparison < Base
|
19
21
|
extend AutoCorrector
|
20
22
|
|
21
|
-
MSG = 'Use
|
22
|
-
RESTRICT_ON_SEND = %i[==].freeze
|
23
|
+
MSG = 'Use `%<bang>sequal?` instead of `%<comparison_method>s` when comparing `object_id`.'
|
24
|
+
RESTRICT_ON_SEND = %i[== !=].freeze
|
25
|
+
|
26
|
+
# @!method object_id_comparison(node)
|
27
|
+
def_node_matcher :object_id_comparison, <<~PATTERN
|
28
|
+
(send
|
29
|
+
(send
|
30
|
+
_lhs_receiver :object_id) ${:== :!=}
|
31
|
+
(send
|
32
|
+
_rhs_receiver :object_id))
|
33
|
+
PATTERN
|
23
34
|
|
24
35
|
def on_send(node)
|
25
|
-
return unless
|
36
|
+
return unless (comparison_method = object_id_comparison(node))
|
26
37
|
|
27
|
-
|
38
|
+
bang = comparison_method == :== ? '' : '!'
|
39
|
+
add_offense(node,
|
40
|
+
message: format(MSG, comparison_method: comparison_method,
|
41
|
+
bang: bang)) do |corrector|
|
28
42
|
receiver = node.receiver.receiver
|
29
43
|
argument = node.first_argument.receiver
|
30
44
|
return unless receiver && argument
|
31
45
|
|
32
|
-
replacement = "#{receiver.source}.equal?(#{argument.source})"
|
46
|
+
replacement = "#{bang}#{receiver.source}.equal?(#{argument.source})"
|
33
47
|
|
34
48
|
corrector.replace(node, replacement)
|
35
49
|
end
|
36
50
|
end
|
37
|
-
|
38
|
-
private
|
39
|
-
|
40
|
-
def compare_between_object_id_by_double_equal?(node)
|
41
|
-
object_id_method?(node.receiver) && object_id_method?(node.first_argument)
|
42
|
-
end
|
43
|
-
|
44
|
-
def object_id_method?(node)
|
45
|
-
node.send_type? && node.method?(:object_id)
|
46
|
-
end
|
47
51
|
end
|
48
52
|
end
|
49
53
|
end
|
@@ -240,30 +240,22 @@ module RuboCop
|
|
240
240
|
def correct_if_node(node, cond)
|
241
241
|
result = condition_evaluation(node, cond)
|
242
242
|
|
243
|
-
if node.elsif? && result
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
elsif node.else? || node.ternary?
|
260
|
-
add_offense(cond) do |corrector|
|
261
|
-
corrector.replace(node, node.else_branch.source)
|
262
|
-
end
|
263
|
-
else
|
264
|
-
add_offense(cond) do |corrector|
|
265
|
-
corrector.remove(node)
|
266
|
-
end
|
243
|
+
new_node = if node.elsif? && result
|
244
|
+
"else\n #{range_with_comments(node.if_branch).source}"
|
245
|
+
elsif node.elsif? && !result
|
246
|
+
"else\n #{node.else_branch.source}"
|
247
|
+
elsif node.if_branch && result
|
248
|
+
node.if_branch.source
|
249
|
+
elsif node.elsif_conditional?
|
250
|
+
"#{node.else_branch.source.sub('elsif', 'if')}\nend"
|
251
|
+
elsif node.else? || node.ternary?
|
252
|
+
node.else_branch.source
|
253
|
+
else
|
254
|
+
'' # Equivalent to removing the node
|
255
|
+
end
|
256
|
+
|
257
|
+
add_offense(cond) do |corrector|
|
258
|
+
corrector.replace(node, new_node)
|
267
259
|
end
|
268
260
|
end
|
269
261
|
# rubocop:enable Metrics/AbcSize, Metrics/MethodLength, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
@@ -8,6 +8,11 @@ module RuboCop
|
|
8
8
|
# given by `ruby -cw` prior to Ruby 2.6:
|
9
9
|
# "shadowing outer local variable - foo".
|
10
10
|
#
|
11
|
+
# The cop is now disabled by default to match the upstream Ruby behavior.
|
12
|
+
# It's useful, however, if you'd like to avoid shadowing variables from outer
|
13
|
+
# scopes, which some people consider an anti-pattern that makes it harder
|
14
|
+
# to keep track of what's going on in a program.
|
15
|
+
#
|
11
16
|
# NOTE: Shadowing of variables in block passed to `Ractor.new` is allowed
|
12
17
|
# because `Ractor` should not access outer variables.
|
13
18
|
# eg. following style is encouraged:
|
@@ -0,0 +1,87 @@
|
|
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
|
+
# @safety
|
14
|
+
# This cop is unsafe because the receiver could have nonstandard implementation
|
15
|
+
# of `fetch`, or be a class other than the one listed above.
|
16
|
+
#
|
17
|
+
# It is also unsafe because default value argument could have side effects:
|
18
|
+
#
|
19
|
+
# [source,ruby]
|
20
|
+
# ----
|
21
|
+
# def x(a) = puts "side effect"
|
22
|
+
# Array.new(5, x(1)) { 2 }
|
23
|
+
# ----
|
24
|
+
#
|
25
|
+
# so removing it would change behavior.
|
26
|
+
#
|
27
|
+
# @example
|
28
|
+
# # bad
|
29
|
+
# x.fetch(key, default_value) { block_value }
|
30
|
+
# Array.new(size, default_value) { block_value }
|
31
|
+
#
|
32
|
+
# # good
|
33
|
+
# x.fetch(key) { block_value }
|
34
|
+
# Array.new(size) { block_value }
|
35
|
+
#
|
36
|
+
# # also good - in case default value argument is desired instead
|
37
|
+
# x.fetch(key, default_value)
|
38
|
+
# Array.new(size, default_value)
|
39
|
+
#
|
40
|
+
# # good - keyword arguments aren't registered as offenses
|
41
|
+
# x.fetch(key, keyword: :arg) { block_value }
|
42
|
+
#
|
43
|
+
# @example AllowedReceivers: ['Rails.cache']
|
44
|
+
# # good
|
45
|
+
# Rails.cache.fetch(name, options) { block }
|
46
|
+
#
|
47
|
+
class UselessDefaultValueArgument < Base
|
48
|
+
include AllowedReceivers
|
49
|
+
extend AutoCorrector
|
50
|
+
|
51
|
+
MSG = 'Block supersedes default value argument.'
|
52
|
+
|
53
|
+
RESTRICT_ON_SEND = %i[fetch new].freeze
|
54
|
+
|
55
|
+
# @!method default_value_argument_and_block(node)
|
56
|
+
def_node_matcher :default_value_argument_and_block, <<~PATTERN
|
57
|
+
(any_block
|
58
|
+
{
|
59
|
+
(call _receiver :fetch $_key $_default_value)
|
60
|
+
(send (const _ :Array) :new $_size $_default_value)
|
61
|
+
}
|
62
|
+
_args
|
63
|
+
_block_body)
|
64
|
+
PATTERN
|
65
|
+
|
66
|
+
def on_send(node)
|
67
|
+
unless (prev_arg_node, default_value_node = default_value_argument_and_block(node.parent))
|
68
|
+
return
|
69
|
+
end
|
70
|
+
return if allowed_receiver?(node.receiver)
|
71
|
+
return if hash_without_braces?(default_value_node)
|
72
|
+
|
73
|
+
add_offense(default_value_node) do |corrector|
|
74
|
+
corrector.remove(prev_arg_node.source_range.end.join(default_value_node.source_range))
|
75
|
+
end
|
76
|
+
end
|
77
|
+
alias on_csend on_send
|
78
|
+
|
79
|
+
private
|
80
|
+
|
81
|
+
def hash_without_braces?(node)
|
82
|
+
node.hash_type? && !node.braces?
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
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
|
@@ -39,7 +39,7 @@ module RuboCop
|
|
39
39
|
class AbcSize < Base
|
40
40
|
include MethodComplexity
|
41
41
|
|
42
|
-
MSG = 'Assignment Branch Condition size for
|
42
|
+
MSG = 'Assignment Branch Condition size for `%<method>s` is too high. ' \
|
43
43
|
'[%<abc_vector>s %<complexity>.4g/%<max>.4g]'
|
44
44
|
|
45
45
|
private
|
@@ -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
|
@@ -0,0 +1,216 @@
|
|
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. The cop does
|
14
|
+
# not make an assessment if the return type is unknown (method calls, variables, etc.).
|
15
|
+
#
|
16
|
+
# NOTE: Operator methods (`def ==`, etc.) are ignored.
|
17
|
+
#
|
18
|
+
# By default, the cop runs in `conservative` mode, which allows a method to be named
|
19
|
+
# with a question mark as long as at least one return value is boolean. In `aggressive`
|
20
|
+
# mode, methods with a question mark will register an offense if any known non-boolean
|
21
|
+
# return values are detected.
|
22
|
+
#
|
23
|
+
# The cop also has `AllowedMethods` configuration in order to prevent the cop from
|
24
|
+
# registering an offense from a method name that does not confirm to the naming
|
25
|
+
# guidelines. By default, `call` is allowed.
|
26
|
+
#
|
27
|
+
# @example Mode: conservative (default)
|
28
|
+
# # bad
|
29
|
+
# def foo
|
30
|
+
# bar == baz
|
31
|
+
# end
|
32
|
+
#
|
33
|
+
# # good
|
34
|
+
# def foo?
|
35
|
+
# bar == baz
|
36
|
+
# end
|
37
|
+
#
|
38
|
+
# # bad
|
39
|
+
# def foo?
|
40
|
+
# 5
|
41
|
+
# end
|
42
|
+
#
|
43
|
+
# # good
|
44
|
+
# def foo
|
45
|
+
# 5
|
46
|
+
# end
|
47
|
+
#
|
48
|
+
# # good - operator method
|
49
|
+
# def ==(other)
|
50
|
+
# hash == other.hash
|
51
|
+
# end
|
52
|
+
#
|
53
|
+
# # good - at least one return value is boolean
|
54
|
+
# def foo?
|
55
|
+
# return unless bar?
|
56
|
+
# true
|
57
|
+
# end
|
58
|
+
#
|
59
|
+
# # ok - return type is not known
|
60
|
+
# def foo?
|
61
|
+
# bar
|
62
|
+
# end
|
63
|
+
#
|
64
|
+
# # ok - return type is not known
|
65
|
+
# def foo
|
66
|
+
# bar?
|
67
|
+
# end
|
68
|
+
#
|
69
|
+
# @example Mode: aggressive
|
70
|
+
# # bad - the method returns nil in some cases
|
71
|
+
# def foo?
|
72
|
+
# return unless bar?
|
73
|
+
# true
|
74
|
+
# end
|
75
|
+
#
|
76
|
+
class PredicateMethod < Base
|
77
|
+
include AllowedMethods
|
78
|
+
|
79
|
+
MSG_PREDICATE = 'Predicate method names should end with `?`.'
|
80
|
+
MSG_NON_PREDICATE = 'Non-predicate method names should not end with `?`.'
|
81
|
+
|
82
|
+
def on_def(node)
|
83
|
+
return if allowed?(node)
|
84
|
+
|
85
|
+
return_values = return_values(node.body)
|
86
|
+
return if acceptable?(return_values)
|
87
|
+
|
88
|
+
if node.predicate_method? && potential_non_predicate?(return_values)
|
89
|
+
add_offense(node.loc.name, message: MSG_NON_PREDICATE)
|
90
|
+
elsif !node.predicate_method? && all_return_values_boolean?(return_values)
|
91
|
+
add_offense(node.loc.name, message: MSG_PREDICATE)
|
92
|
+
end
|
93
|
+
end
|
94
|
+
alias on_defs on_def
|
95
|
+
|
96
|
+
private
|
97
|
+
|
98
|
+
def allowed?(node)
|
99
|
+
allowed_method?(node.method_name) ||
|
100
|
+
node.operator_method? ||
|
101
|
+
node.body.nil?
|
102
|
+
end
|
103
|
+
|
104
|
+
def acceptable?(return_values)
|
105
|
+
# In `conservative` mode, if the method returns `super`, `zsuper`, or a
|
106
|
+
# non-comparison method call, the method name is acceptable.
|
107
|
+
return false unless conservative?
|
108
|
+
|
109
|
+
return_values.any? do |value|
|
110
|
+
value.type?(:super, :zsuper) || non_comparison_call?(value)
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
def non_comparison_call?(value)
|
115
|
+
value.call_type? && !value.comparison_method?
|
116
|
+
end
|
117
|
+
|
118
|
+
def return_values(node)
|
119
|
+
# Collect all the (implicit and explicit) return values of a node
|
120
|
+
return_values = Set.new(node.begin_type? ? [] : [extract_return_value(node)])
|
121
|
+
|
122
|
+
node.each_descendant(:return) do |return_node|
|
123
|
+
return_values << extract_return_value(return_node)
|
124
|
+
end
|
125
|
+
|
126
|
+
last_value = last_value(node)
|
127
|
+
return_values << last_value if last_value
|
128
|
+
|
129
|
+
process_return_values(return_values)
|
130
|
+
end
|
131
|
+
|
132
|
+
def all_return_values_boolean?(return_values)
|
133
|
+
values = return_values.reject { |value| value.type?(:super, :zsuper) }
|
134
|
+
return false if values.empty?
|
135
|
+
|
136
|
+
values.all? { |value| boolean_return?(value) }
|
137
|
+
end
|
138
|
+
|
139
|
+
def boolean_return?(value)
|
140
|
+
value.boolean_type? || (value.call_type? && value.comparison_method?)
|
141
|
+
end
|
142
|
+
|
143
|
+
def potential_non_predicate?(return_values)
|
144
|
+
# Assumes a method to be non-predicate if all return values are non-boolean literals.
|
145
|
+
#
|
146
|
+
# In `Mode: conservative`, if any of the return values is a boolean,
|
147
|
+
# the method name is acceptable.
|
148
|
+
# In `Mode: aggressive`, all return values must be booleans for a predicate
|
149
|
+
# method, or else an offense will be registered.
|
150
|
+
return false if conservative? && return_values.any? { |value| boolean_return?(value) }
|
151
|
+
|
152
|
+
return_values.any? do |value|
|
153
|
+
value.literal? && !value.boolean_type?
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
def extract_return_value(node)
|
158
|
+
return node unless node.return_type?
|
159
|
+
|
160
|
+
# `return` without a value is a `nil` return.
|
161
|
+
return s(:nil) if node.arguments.empty?
|
162
|
+
|
163
|
+
# When there's a multiple return, it cannot be a predicate
|
164
|
+
# so just return an `array` sexp for simplicity.
|
165
|
+
return s(:array) unless node.arguments.one?
|
166
|
+
|
167
|
+
node.first_argument
|
168
|
+
end
|
169
|
+
|
170
|
+
def last_value(node)
|
171
|
+
value = node.begin_type? ? node.children.last : node
|
172
|
+
value.return_type? ? extract_return_value(value) : value
|
173
|
+
end
|
174
|
+
|
175
|
+
def process_return_values(return_values)
|
176
|
+
return_values.flat_map do |value|
|
177
|
+
if value.conditional?
|
178
|
+
process_return_values(extract_conditional_branches(value))
|
179
|
+
elsif and_or?(value)
|
180
|
+
process_return_values(extract_and_or_clauses(value))
|
181
|
+
else
|
182
|
+
value
|
183
|
+
end
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
def and_or?(node)
|
188
|
+
node.type?(:and, :or)
|
189
|
+
end
|
190
|
+
|
191
|
+
def extract_and_or_clauses(node)
|
192
|
+
# Recursively traverse an `and` or `or` node to collect all clauses within
|
193
|
+
return node unless and_or?(node)
|
194
|
+
|
195
|
+
[extract_and_or_clauses(node.lhs), extract_and_or_clauses(node.rhs)].flatten
|
196
|
+
end
|
197
|
+
|
198
|
+
def extract_conditional_branches(node)
|
199
|
+
return node unless node.conditional?
|
200
|
+
|
201
|
+
if node.type?(:while, :until)
|
202
|
+
# If there is no body, act as implicit `nil`.
|
203
|
+
node.body ? [last_value(node.body)] : [s(:nil)]
|
204
|
+
else
|
205
|
+
# Branches with no value act as an implicit `nil`.
|
206
|
+
node.branches.filter_map { |branch| branch ? last_value(branch) : s(:nil) }
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
def conservative?
|
211
|
+
cop_config.fetch('Mode', :conservative).to_sym == :conservative
|
212
|
+
end
|
213
|
+
end
|
214
|
+
end
|
215
|
+
end
|
216
|
+
end
|
@@ -100,7 +100,7 @@ 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)
|
@@ -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,15 +195,27 @@ module RuboCop
|
|
195
195
|
def autocorrect(corrector, node)
|
196
196
|
case style
|
197
197
|
when :group
|
198
|
-
|
199
|
-
return unless def_nodes.any?
|
200
|
-
|
201
|
-
replace_defs(corrector, node, def_nodes)
|
198
|
+
autocorrect_group_style(corrector, node)
|
202
199
|
when :inline
|
200
|
+
autocorrect_inline_style(corrector, node)
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
def autocorrect_group_style(corrector, node)
|
205
|
+
def_nodes = find_corresponding_def_nodes(node)
|
206
|
+
return unless def_nodes.any?
|
207
|
+
|
208
|
+
replace_defs(corrector, node, def_nodes)
|
209
|
+
end
|
210
|
+
|
211
|
+
def autocorrect_inline_style(corrector, node)
|
212
|
+
if node.parent&.begin_type?
|
213
|
+
remove_modifier_node_within_begin(corrector, node, node.parent)
|
214
|
+
else
|
203
215
|
remove_nodes(corrector, node)
|
204
|
-
|
205
|
-
|
206
|
-
|
216
|
+
end
|
217
|
+
select_grouped_def_nodes(node).each do |grouped_def_node|
|
218
|
+
insert_inline_modifier(corrector, grouped_def_node, node.method_name)
|
207
219
|
end
|
208
220
|
end
|
209
221
|
|
@@ -224,9 +236,13 @@ module RuboCop
|
|
224
236
|
end
|
225
237
|
|
226
238
|
def offense?(node)
|
227
|
-
|
228
|
-
|
229
|
-
|
239
|
+
if group_style?
|
240
|
+
return false if node.parent ? node.parent.if_type? : access_modifier_with_symbol?(node)
|
241
|
+
|
242
|
+
access_modifier_is_inlined?(node) && !right_siblings_same_inline_method?(node)
|
243
|
+
else
|
244
|
+
access_modifier_is_not_inlined?(node) && select_grouped_def_nodes(node).any?
|
245
|
+
end
|
230
246
|
end
|
231
247
|
|
232
248
|
def correctable_group_offense?(node)
|
@@ -331,6 +347,12 @@ module RuboCop
|
|
331
347
|
end
|
332
348
|
end
|
333
349
|
|
350
|
+
def remove_modifier_node_within_begin(corrector, modifier_node, begin_node)
|
351
|
+
def_node = begin_node.children[1]
|
352
|
+
range = modifier_node.source_range.begin.join(def_node.source_range.begin)
|
353
|
+
corrector.remove(range)
|
354
|
+
end
|
355
|
+
|
334
356
|
def def_source(node, def_nodes)
|
335
357
|
[
|
336
358
|
*processed_source.ast_with_comments[node].map(&:text),
|