rubocop 1.75.8 → 1.76.1
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 +50 -7
- data/config/obsoletion.yml +6 -3
- data/lib/rubocop/cop/bundler/ordered_gems.rb +1 -1
- data/lib/rubocop/cop/gemspec/duplicated_assignment.rb +2 -2
- 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/lint/empty_interpolation.rb +3 -1
- 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/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/empty_string_inside_interpolation.rb +100 -0
- data/lib/rubocop/cop/style/if_unless_modifier.rb +2 -4
- data/lib/rubocop/cop/style/it_block_parameter.rb +33 -14
- data/lib/rubocop/cop/style/method_call_with_args_parentheses/omit_parentheses.rb +1 -1
- data/lib/rubocop/cop/style/redundant_array_flatten.rb +50 -0
- data/lib/rubocop/cop/style/redundant_parentheses.rb +17 -5
- data/lib/rubocop/cop/style/safe_navigation.rb +24 -11
- data/lib/rubocop/rspec/expect_offense.rb +9 -3
- data/lib/rubocop/version.rb +1 -1
- data/lib/rubocop.rb +6 -1
- metadata +12 -7
@@ -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
|
@@ -0,0 +1,100 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module Style
|
6
|
+
# Checks for empty strings being assigned inside string interpolation.
|
7
|
+
#
|
8
|
+
# Empty strings are a meaningless outcome inside of string interpolation, so we remove them.
|
9
|
+
# Alternatively, when configured to do so, we prioritise using empty strings.
|
10
|
+
#
|
11
|
+
# While this cop would also apply to variables that are only going to be used as strings,
|
12
|
+
# RuboCop can't detect that, so we only check inside of string interpolation.
|
13
|
+
#
|
14
|
+
# @example EnforcedStyle: trailing_conditional (default)
|
15
|
+
# # bad
|
16
|
+
# "#{condition ? 'foo' : ''}"
|
17
|
+
#
|
18
|
+
# # good
|
19
|
+
# "#{'foo' if condition}"
|
20
|
+
#
|
21
|
+
# # bad
|
22
|
+
# "#{condition ? '' : 'foo'}"
|
23
|
+
#
|
24
|
+
# # good
|
25
|
+
# "#{'foo' unless condition}"
|
26
|
+
#
|
27
|
+
# @example EnforcedStyle: ternary
|
28
|
+
# # bad
|
29
|
+
# "#{'foo' if condition}"
|
30
|
+
#
|
31
|
+
# # good
|
32
|
+
# "#{condition ? 'foo' : ''}"
|
33
|
+
#
|
34
|
+
# # bad
|
35
|
+
# "#{'foo' unless condition}"
|
36
|
+
#
|
37
|
+
# # good
|
38
|
+
# "#{condition ? '' : 'foo'}"
|
39
|
+
#
|
40
|
+
class EmptyStringInsideInterpolation < Base
|
41
|
+
include ConfigurableEnforcedStyle
|
42
|
+
include Interpolation
|
43
|
+
extend AutoCorrector
|
44
|
+
|
45
|
+
MSG_TRAILING_CONDITIONAL = 'Do not use trailing conditionals in string interpolation.'
|
46
|
+
MSG_TERNARY = 'Do not return empty strings in string interpolation.'
|
47
|
+
|
48
|
+
# rubocop:disable Metrics/AbcSize, Metrics/MethodLength, Metrics/PerceivedComplexity
|
49
|
+
def on_interpolation(node)
|
50
|
+
node.each_child_node(:if) do |child_node|
|
51
|
+
if style == :trailing_conditional
|
52
|
+
if empty_if_outcome?(child_node)
|
53
|
+
ternary_style_autocorrect(child_node, child_node.else_branch.source, 'unless')
|
54
|
+
end
|
55
|
+
|
56
|
+
if empty_else_outcome?(child_node)
|
57
|
+
ternary_style_autocorrect(child_node, child_node.if_branch.source, 'if')
|
58
|
+
end
|
59
|
+
elsif style == :ternary
|
60
|
+
next unless child_node.modifier_form?
|
61
|
+
|
62
|
+
ternary_component = if child_node.unless?
|
63
|
+
"'' : #{child_node.if_branch.source}"
|
64
|
+
else
|
65
|
+
"#{child_node.if_branch.source} : ''"
|
66
|
+
end
|
67
|
+
|
68
|
+
add_offense(node, message: MSG_TRAILING_CONDITIONAL) do |corrector|
|
69
|
+
corrector.replace(node, "\#{#{child_node.condition.source} ? #{ternary_component}}")
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
# rubocop:enable Metrics/AbcSize, Metrics/MethodLength, Metrics/PerceivedComplexity
|
75
|
+
|
76
|
+
private
|
77
|
+
|
78
|
+
def empty_if_outcome?(node)
|
79
|
+
empty_branch_outcome?(node.if_branch)
|
80
|
+
end
|
81
|
+
|
82
|
+
def empty_else_outcome?(node)
|
83
|
+
empty_branch_outcome?(node.else_branch)
|
84
|
+
end
|
85
|
+
|
86
|
+
def empty_branch_outcome?(branch)
|
87
|
+
return false unless branch
|
88
|
+
|
89
|
+
branch.nil_type? || (branch.str_type? && branch.value.empty?)
|
90
|
+
end
|
91
|
+
|
92
|
+
def ternary_style_autocorrect(node, outcome, condition)
|
93
|
+
add_offense(node, message: MSG_TERNARY) do |corrector|
|
94
|
+
corrector.replace(node, "#{outcome} #{condition} #{node.condition.source}")
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
@@ -132,12 +132,10 @@ module RuboCop
|
|
132
132
|
end
|
133
133
|
|
134
134
|
def pattern_matching_nodes(condition)
|
135
|
-
if condition.
|
135
|
+
if condition.any_match_pattern_type?
|
136
136
|
[condition]
|
137
137
|
else
|
138
|
-
condition.each_descendant.select
|
139
|
-
node.type?(:match_pattern, :match_pattern_p)
|
140
|
-
end
|
138
|
+
condition.each_descendant.select(&:any_match_pattern_type?)
|
141
139
|
end
|
142
140
|
end
|
143
141
|
|
@@ -5,15 +5,28 @@ module RuboCop
|
|
5
5
|
module Style
|
6
6
|
# Checks for blocks with one argument where `it` block parameter can be used.
|
7
7
|
#
|
8
|
-
# It provides
|
8
|
+
# It provides four `EnforcedStyle` options:
|
9
9
|
#
|
10
|
-
# 1. `
|
11
|
-
# 2. `
|
12
|
-
# 3. `
|
10
|
+
# 1. `allow_single_line` (default) ... Always uses the `it` block parameter in a single line.
|
11
|
+
# 2. `only_numbered_parameters` ... Detects only numbered block parameters.
|
12
|
+
# 3. `always` ... Always uses the `it` block parameter.
|
13
|
+
# 4. `disallow` ... Disallows the `it` block parameter.
|
13
14
|
#
|
14
|
-
# A single numbered parameter is detected when `
|
15
|
+
# A single numbered parameter is detected when `allow_single_line`,
|
16
|
+
# `only_numbered_parameters`, or `always`.
|
15
17
|
#
|
16
|
-
# @example EnforcedStyle:
|
18
|
+
# @example EnforcedStyle: allow_single_line (default)
|
19
|
+
# # bad
|
20
|
+
# block do
|
21
|
+
# do_something(it)
|
22
|
+
# end
|
23
|
+
# block { do_something(_1) }
|
24
|
+
#
|
25
|
+
# # good
|
26
|
+
# block { do_something(it) }
|
27
|
+
# block { |named_param| do_something(named_param) }
|
28
|
+
#
|
29
|
+
# @example EnforcedStyle: only_numbered_parameters
|
17
30
|
# # bad
|
18
31
|
# block { do_something(_1) }
|
19
32
|
#
|
@@ -42,8 +55,9 @@ module RuboCop
|
|
42
55
|
extend TargetRubyVersion
|
43
56
|
extend AutoCorrector
|
44
57
|
|
45
|
-
|
46
|
-
|
58
|
+
MSG_USE_IT_PARAMETER = 'Use `it` block parameter.'
|
59
|
+
MSG_AVOID_IT_PARAMETER = 'Avoid using `it` block parameter.'
|
60
|
+
MSG_AVOID_IT_PARAMETER_MULTI_LINE = 'Avoid using numbered parameters for multi-line blocks.'
|
47
61
|
|
48
62
|
minimum_target_ruby_version 3.4
|
49
63
|
|
@@ -57,7 +71,7 @@ module RuboCop
|
|
57
71
|
variables = find_block_variables(node, node.first_argument.source)
|
58
72
|
|
59
73
|
variables.each do |variable|
|
60
|
-
add_offense(variable, message:
|
74
|
+
add_offense(variable, message: MSG_USE_IT_PARAMETER) do |corrector|
|
61
75
|
corrector.remove(node.arguments)
|
62
76
|
corrector.replace(variable, 'it')
|
63
77
|
end
|
@@ -71,19 +85,24 @@ module RuboCop
|
|
71
85
|
variables = find_block_variables(node, '_1')
|
72
86
|
|
73
87
|
variables.each do |variable|
|
74
|
-
add_offense(variable, message:
|
88
|
+
add_offense(variable, message: MSG_USE_IT_PARAMETER) do |corrector|
|
75
89
|
corrector.replace(variable, 'it')
|
76
90
|
end
|
77
91
|
end
|
78
92
|
end
|
79
93
|
|
80
94
|
def on_itblock(node)
|
81
|
-
|
95
|
+
case style
|
96
|
+
when :allow_single_line
|
97
|
+
return if node.single_line?
|
82
98
|
|
83
|
-
|
99
|
+
add_offense(node, message: MSG_AVOID_IT_PARAMETER_MULTI_LINE)
|
100
|
+
when :disallow
|
101
|
+
variables = find_block_variables(node, 'it')
|
84
102
|
|
85
|
-
|
86
|
-
|
103
|
+
variables.each do |variable|
|
104
|
+
add_offense(variable, message: MSG_AVOID_IT_PARAMETER)
|
105
|
+
end
|
87
106
|
end
|
88
107
|
end
|
89
108
|
|
@@ -0,0 +1,50 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module Style
|
6
|
+
# Checks for redundant calls of `Array#flatten`.
|
7
|
+
#
|
8
|
+
# `Array#join` joins nested arrays recursively, so flattening an array
|
9
|
+
# beforehand is redundant.
|
10
|
+
#
|
11
|
+
# @safety
|
12
|
+
# Cop is unsafe because the receiver of `flatten` method might not
|
13
|
+
# be an `Array`, so it's possible it won't respond to `join` method,
|
14
|
+
# or the end result would be different.
|
15
|
+
# Also, if the global variable `$,` is set to a value other than the default `nil`,
|
16
|
+
# false positives may occur.
|
17
|
+
#
|
18
|
+
# @example
|
19
|
+
# # bad
|
20
|
+
# x.flatten.join
|
21
|
+
# x.flatten(1).join
|
22
|
+
#
|
23
|
+
# # good
|
24
|
+
# x.join
|
25
|
+
#
|
26
|
+
class RedundantArrayFlatten < Base
|
27
|
+
extend AutoCorrector
|
28
|
+
|
29
|
+
MSG = 'Remove the redundant `flatten`.'
|
30
|
+
|
31
|
+
RESTRICT_ON_SEND = %i[flatten].freeze
|
32
|
+
|
33
|
+
# @!method flatten_join?(node)
|
34
|
+
def_node_matcher :flatten_join?, <<~PATTERN
|
35
|
+
(call (call !nil? :flatten _?) :join (nil)?)
|
36
|
+
PATTERN
|
37
|
+
|
38
|
+
def on_send(node)
|
39
|
+
return unless flatten_join?(node.parent)
|
40
|
+
|
41
|
+
range = node.loc.dot.begin.join(node.source_range.end)
|
42
|
+
add_offense(range) do |corrector|
|
43
|
+
corrector.remove(range)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
alias on_csend on_send
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -49,6 +49,7 @@ module RuboCop
|
|
49
49
|
empty_parentheses?(node) ||
|
50
50
|
first_arg_begins_with_hash_literal?(node) ||
|
51
51
|
rescue?(node) ||
|
52
|
+
in_pattern_matching_in_method_argument?(node) ||
|
52
53
|
allowed_pin_operator?(node) ||
|
53
54
|
allowed_expression?(node)
|
54
55
|
end
|
@@ -122,6 +123,13 @@ module RuboCop
|
|
122
123
|
hash_literal && first_argument?(node) && !parentheses?(hash_literal) && !parenthesized
|
123
124
|
end
|
124
125
|
|
126
|
+
def in_pattern_matching_in_method_argument?(begin_node)
|
127
|
+
return false unless begin_node.parent&.call_type?
|
128
|
+
return false unless (node = begin_node.children.first)
|
129
|
+
|
130
|
+
target_ruby_version <= 2.7 ? node.match_pattern_type? : node.match_pattern_p_type?
|
131
|
+
end
|
132
|
+
|
125
133
|
def method_chain_begins_with_hash_literal(node)
|
126
134
|
return if node.nil?
|
127
135
|
return node if node.hash_type?
|
@@ -134,7 +142,7 @@ module RuboCop
|
|
134
142
|
node = begin_node.children.first
|
135
143
|
|
136
144
|
if (message = find_offense_message(begin_node, node))
|
137
|
-
if node.range_type? && !argument_of_parenthesized_method_call?(begin_node)
|
145
|
+
if node.range_type? && !argument_of_parenthesized_method_call?(begin_node, node)
|
138
146
|
begin_node = begin_node.parent
|
139
147
|
end
|
140
148
|
|
@@ -156,8 +164,11 @@ module RuboCop
|
|
156
164
|
if node.lambda_or_proc? && (node.braces? || node.send_node.lambda_literal?)
|
157
165
|
return 'an expression'
|
158
166
|
end
|
167
|
+
if node.any_match_pattern_type? && node.each_ancestor.none?(&:operator_keyword?)
|
168
|
+
return 'a one-line pattern matching'
|
169
|
+
end
|
159
170
|
return 'an interpolated expression' if interpolation?(begin_node)
|
160
|
-
return 'a method argument' if argument_of_parenthesized_method_call?(begin_node)
|
171
|
+
return 'a method argument' if argument_of_parenthesized_method_call?(begin_node, node)
|
161
172
|
|
162
173
|
return if begin_node.chained?
|
163
174
|
|
@@ -180,9 +191,10 @@ module RuboCop
|
|
180
191
|
# @!method interpolation?(node)
|
181
192
|
def_node_matcher :interpolation?, '[^begin ^^dstr]'
|
182
193
|
|
183
|
-
def argument_of_parenthesized_method_call?(begin_node)
|
184
|
-
node
|
185
|
-
|
194
|
+
def argument_of_parenthesized_method_call?(begin_node, node)
|
195
|
+
if node.basic_conditional? || node.rescue_type? || method_call_parentheses_required?(node)
|
196
|
+
return false
|
197
|
+
end
|
186
198
|
return false unless (parent = begin_node.parent)
|
187
199
|
|
188
200
|
parent.call_type? && parent.parenthesized? && parent.receiver != begin_node
|
@@ -86,6 +86,10 @@ module RuboCop
|
|
86
86
|
# foo.baz = bar if foo
|
87
87
|
# foo.baz + bar if foo
|
88
88
|
# foo.bar > 2 if foo
|
89
|
+
#
|
90
|
+
# foo ? foo[index] : nil # Ignored `foo&.[](index)` due to unclear readability benefit.
|
91
|
+
# foo ? foo[idx] = v : nil # Ignored `foo&.[]=(idx, v)` due to unclear readability benefit.
|
92
|
+
# foo ? foo * 42 : nil # Ignored `foo&.*(42)` due to unclear readability benefit.
|
89
93
|
class SafeNavigation < Base # rubocop:disable Metrics/ClassLength
|
90
94
|
include NilMethods
|
91
95
|
include RangeHelp
|
@@ -146,6 +150,7 @@ module RuboCop
|
|
146
150
|
|
147
151
|
body = extract_if_body(node)
|
148
152
|
method_call = receiver.parent
|
153
|
+
return if dotless_operator_call?(method_call) || method_call.double_colon?
|
149
154
|
|
150
155
|
removal_ranges = [begin_range(node, body), end_range(node, body)]
|
151
156
|
|
@@ -181,6 +186,8 @@ module RuboCop
|
|
181
186
|
end
|
182
187
|
end
|
183
188
|
|
189
|
+
private
|
190
|
+
|
184
191
|
def report_offense(node, rhs, rhs_receiver, *removal_ranges, offense_range: node)
|
185
192
|
add_offense(offense_range) do |corrector|
|
186
193
|
next if ignored_node?(node)
|
@@ -198,8 +205,6 @@ module RuboCop
|
|
198
205
|
end
|
199
206
|
end
|
200
207
|
|
201
|
-
private
|
202
|
-
|
203
208
|
def find_method_chain(node)
|
204
209
|
return node unless node&.parent&.call_type?
|
205
210
|
|
@@ -235,7 +240,7 @@ module RuboCop
|
|
235
240
|
return false if !matching_nodes?(lhs_receiver, rhs_receiver) || rhs_receiver.nil?
|
236
241
|
return false if use_var_only_in_unless_modifier?(node, lhs_receiver)
|
237
242
|
return false if chain_length(rhs, rhs_receiver) > max_chain_length
|
238
|
-
return false if unsafe_method_used?(rhs, rhs_receiver.parent)
|
243
|
+
return false if unsafe_method_used?(node, rhs, rhs_receiver.parent)
|
239
244
|
return false if rhs.send_type? && rhs.method?(:empty?)
|
240
245
|
|
241
246
|
true
|
@@ -253,6 +258,12 @@ module RuboCop
|
|
253
258
|
end
|
254
259
|
end
|
255
260
|
|
261
|
+
def dotless_operator_call?(method_call)
|
262
|
+
return false if method_call.loc.dot
|
263
|
+
|
264
|
+
method_call.method?(:[]) || method_call.method?(:[]=) || method_call.operator_method?
|
265
|
+
end
|
266
|
+
|
256
267
|
def handle_comments(corrector, node, method_call)
|
257
268
|
comments = comments(node)
|
258
269
|
return if comments.empty?
|
@@ -334,21 +345,24 @@ module RuboCop
|
|
334
345
|
end
|
335
346
|
end
|
336
347
|
|
337
|
-
def unsafe_method_used?(method_chain, method)
|
338
|
-
return true if unsafe_method?(method)
|
348
|
+
def unsafe_method_used?(node, method_chain, method)
|
349
|
+
return true if unsafe_method?(node, method)
|
339
350
|
|
340
351
|
method.each_ancestor(:send).any? do |ancestor|
|
341
352
|
break true unless config.cop_enabled?('Lint/SafeNavigationChain')
|
342
353
|
|
343
|
-
break true if unsafe_method?(ancestor)
|
354
|
+
break true if unsafe_method?(node, ancestor)
|
344
355
|
break true if nil_methods.include?(ancestor.method_name)
|
345
356
|
break false if ancestor == method_chain
|
346
357
|
end
|
347
358
|
end
|
348
359
|
|
349
|
-
def unsafe_method?(send_node)
|
350
|
-
negated?(send_node)
|
351
|
-
|
360
|
+
def unsafe_method?(node, send_node)
|
361
|
+
return true if negated?(send_node)
|
362
|
+
|
363
|
+
return false if node.respond_to?(:ternary?) && node.ternary?
|
364
|
+
|
365
|
+
send_node.assignment? ||
|
352
366
|
(!send_node.dot? && !send_node.safe_navigation?)
|
353
367
|
end
|
354
368
|
|
@@ -377,8 +391,7 @@ module RuboCop
|
|
377
391
|
method_chain)
|
378
392
|
start_method.each_ancestor do |ancestor|
|
379
393
|
break unless %i[send block].include?(ancestor.type)
|
380
|
-
next
|
381
|
-
next if ancestor.safe_navigation?
|
394
|
+
next if !ancestor.send_type? || ancestor.operator_method?
|
382
395
|
|
383
396
|
corrector.insert_before(ancestor.loc.dot, '&')
|
384
397
|
|
@@ -72,9 +72,15 @@ module RuboCop
|
|
72
72
|
#
|
73
73
|
# expect_no_corrections
|
74
74
|
#
|
75
|
-
# If your code has variables of different lengths, you can use
|
76
|
-
#
|
77
|
-
#
|
75
|
+
# If your code has variables of different lengths, you can use the
|
76
|
+
# following markers to format your template by passing the variables as a
|
77
|
+
# keyword arguments:
|
78
|
+
#
|
79
|
+
# - `%{foo}`: Interpolates `foo`
|
80
|
+
# - `^{foo}`: Inserts `'^' * foo.size` for dynamic offense range length
|
81
|
+
# - `_{foo}`: Inserts `' ' * foo.size` for dynamic offense range indentation
|
82
|
+
#
|
83
|
+
# You can also abbreviate offense messages with `[...]`.
|
78
84
|
#
|
79
85
|
# %w[raise fail].each do |keyword|
|
80
86
|
# expect_offense(<<~RUBY, keyword: keyword)
|