rubocop 1.75.8 → 1.80.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 +20 -16
- data/config/default.yml +107 -26
- data/config/obsoletion.yml +6 -3
- data/exe/rubocop +1 -8
- data/lib/rubocop/cli.rb +17 -1
- data/lib/rubocop/config_loader.rb +1 -38
- data/lib/rubocop/cop/bundler/ordered_gems.rb +1 -1
- data/lib/rubocop/cop/correctors/alignment_corrector.rb +6 -3
- data/lib/rubocop/cop/correctors/for_to_each_corrector.rb +7 -2
- 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/example_description.rb +1 -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/internal_affairs/node_type_group.rb +3 -2
- data/lib/rubocop/cop/internal_affairs/useless_restrict_on_send.rb +1 -1
- data/lib/rubocop/cop/layout/closing_parenthesis_indentation.rb +1 -1
- data/lib/rubocop/cop/layout/empty_lines_after_module_inclusion.rb +101 -0
- data/lib/rubocop/cop/layout/empty_lines_around_access_modifier.rb +1 -1
- data/lib/rubocop/cop/layout/empty_lines_around_arguments.rb +8 -29
- data/lib/rubocop/cop/layout/empty_lines_around_class_body.rb +1 -1
- data/lib/rubocop/cop/layout/line_length.rb +26 -5
- data/lib/rubocop/cop/layout/multiline_operation_indentation.rb +2 -0
- data/lib/rubocop/cop/layout/space_around_keyword.rb +6 -1
- data/lib/rubocop/cop/layout/space_around_operators.rb +8 -0
- 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 +25 -4
- data/lib/rubocop/cop/lint/duplicate_regexp_character_class_element.rb +5 -42
- data/lib/rubocop/cop/lint/empty_interpolation.rb +3 -1
- data/lib/rubocop/cop/lint/float_comparison.rb +4 -4
- data/lib/rubocop/cop/lint/identity_comparison.rb +19 -15
- data/lib/rubocop/cop/lint/literal_as_condition.rb +34 -28
- data/lib/rubocop/cop/lint/missing_cop_enable_directive.rb +1 -2
- data/lib/rubocop/cop/lint/numeric_operation_with_constant_result.rb +1 -0
- data/lib/rubocop/cop/lint/redundant_regexp_quantifiers.rb +1 -1
- data/lib/rubocop/cop/lint/redundant_safe_navigation.rb +101 -2
- data/lib/rubocop/cop/lint/redundant_type_conversion.rb +4 -4
- data/lib/rubocop/cop/lint/require_range_parentheses.rb +1 -1
- data/lib/rubocop/cop/lint/rescue_type.rb +1 -1
- data/lib/rubocop/cop/lint/safe_navigation_chain.rb +4 -4
- data/lib/rubocop/cop/lint/self_assignment.rb +30 -4
- 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_numeric_operation.rb +1 -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/lint/utils/nil_receiver_checker.rb +121 -0
- data/lib/rubocop/cop/mixin/alignment.rb +1 -1
- data/lib/rubocop/cop/mixin/end_keyword_alignment.rb +1 -7
- 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/method_name.rb +127 -13
- data/lib/rubocop/cop/naming/predicate_method.rb +306 -0
- data/lib/rubocop/cop/naming/{predicate_name.rb → predicate_prefix.rb} +4 -4
- data/lib/rubocop/cop/security/eval.rb +2 -1
- data/lib/rubocop/cop/security/open.rb +1 -0
- data/lib/rubocop/cop/style/access_modifier_declarations.rb +1 -1
- data/lib/rubocop/cop/style/accessor_grouping.rb +13 -1
- data/lib/rubocop/cop/style/arguments_forwarding.rb +11 -17
- data/lib/rubocop/cop/style/array_intersect.rb +98 -34
- data/lib/rubocop/cop/style/bitwise_predicate.rb +8 -1
- data/lib/rubocop/cop/style/block_delimiters.rb +1 -1
- 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 +4 -2
- data/lib/rubocop/cop/style/dig_chain.rb +1 -1
- data/lib/rubocop/cop/style/empty_string_inside_interpolation.rb +100 -0
- data/lib/rubocop/cop/style/exponential_notation.rb +3 -2
- data/lib/rubocop/cop/style/fetch_env_var.rb +32 -6
- data/lib/rubocop/cop/style/hash_conversion.rb +16 -8
- data/lib/rubocop/cop/style/if_unless_modifier.rb +13 -6
- data/lib/rubocop/cop/style/infinite_loop.rb +1 -1
- data/lib/rubocop/cop/style/inverse_methods.rb +1 -1
- data/lib/rubocop/cop/style/it_assignment.rb +69 -12
- data/lib/rubocop/cop/style/it_block_parameter.rb +36 -15
- data/lib/rubocop/cop/style/map_to_hash.rb +1 -3
- data/lib/rubocop/cop/style/map_to_set.rb +1 -3
- data/lib/rubocop/cop/style/method_call_with_args_parentheses/omit_parentheses.rb +4 -6
- data/lib/rubocop/cop/style/method_call_with_args_parentheses.rb +16 -0
- data/lib/rubocop/cop/style/min_max_comparison.rb +13 -5
- data/lib/rubocop/cop/style/parallel_assignment.rb +32 -20
- data/lib/rubocop/cop/style/redundant_array_flatten.rb +50 -0
- data/lib/rubocop/cop/style/redundant_begin.rb +34 -0
- data/lib/rubocop/cop/style/redundant_condition.rb +1 -1
- data/lib/rubocop/cop/style/redundant_fetch_block.rb +1 -9
- data/lib/rubocop/cop/style/redundant_freeze.rb +1 -1
- data/lib/rubocop/cop/style/redundant_interpolation.rb +1 -1
- data/lib/rubocop/cop/style/redundant_line_continuation.rb +1 -1
- data/lib/rubocop/cop/style/redundant_parentheses.rb +42 -6
- data/lib/rubocop/cop/style/redundant_self.rb +8 -5
- data/lib/rubocop/cop/style/safe_navigation.rb +38 -12
- data/lib/rubocop/cop/style/single_line_methods.rb +7 -4
- data/lib/rubocop/cop/style/sole_nested_conditional.rb +32 -2
- data/lib/rubocop/cop/style/stabby_lambda_parentheses.rb +1 -1
- data/lib/rubocop/cop/style/string_concatenation.rb +1 -1
- data/lib/rubocop/cop/style/symbol_array.rb +1 -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/variable_force/variable.rb +1 -1
- data/lib/rubocop/cop/variable_force.rb +25 -8
- data/lib/rubocop/cops_documentation_generator.rb +1 -0
- data/lib/rubocop/formatter/disabled_config_formatter.rb +18 -5
- data/lib/rubocop/formatter/fuubar_style_formatter.rb +1 -1
- data/lib/rubocop/formatter/markdown_formatter.rb +1 -0
- data/lib/rubocop/formatter/offense_count_formatter.rb +1 -1
- data/lib/rubocop/formatter/pacman_formatter.rb +1 -0
- data/lib/rubocop/lsp/diagnostic.rb +4 -4
- data/lib/rubocop/lsp/routes.rb +35 -6
- data/lib/rubocop/pending_cops_reporter.rb +56 -0
- data/lib/rubocop/result_cache.rb +14 -12
- data/lib/rubocop/rspec/expect_offense.rb +9 -3
- data/lib/rubocop/runner.rb +6 -4
- data/lib/rubocop/server/cache.rb +4 -2
- data/lib/rubocop/server/client_command/base.rb +10 -0
- data/lib/rubocop/server/client_command/exec.rb +2 -1
- data/lib/rubocop/server/client_command/start.rb +11 -1
- data/lib/rubocop/target_finder.rb +9 -9
- data/lib/rubocop/version.rb +1 -1
- data/lib/rubocop.rb +11 -1
- data/lib/ruby_lsp/rubocop/addon.rb +2 -2
- metadata +17 -7
@@ -6,6 +6,10 @@ module RuboCop
|
|
6
6
|
# Checks for duplicated instance (or singleton) method
|
7
7
|
# definitions.
|
8
8
|
#
|
9
|
+
# NOTE: Aliasing a method to itself is allowed, as it indicates that
|
10
|
+
# the developer intends to suppress Ruby's method redefinition warnings.
|
11
|
+
# See https://bugs.ruby-lang.org/issues/13574.
|
12
|
+
#
|
9
13
|
# @example
|
10
14
|
#
|
11
15
|
# # bad
|
@@ -40,6 +44,18 @@ module RuboCop
|
|
40
44
|
#
|
41
45
|
# alias bar foo
|
42
46
|
#
|
47
|
+
# # good
|
48
|
+
# alias foo foo
|
49
|
+
# def foo
|
50
|
+
# 1
|
51
|
+
# end
|
52
|
+
#
|
53
|
+
# # good
|
54
|
+
# alias_method :foo, :foo
|
55
|
+
# def foo
|
56
|
+
# 1
|
57
|
+
# end
|
58
|
+
#
|
43
59
|
# @example AllCops:ActiveSupportExtensionsEnabled: false (default)
|
44
60
|
#
|
45
61
|
# # good
|
@@ -113,11 +129,13 @@ module RuboCop
|
|
113
129
|
|
114
130
|
# @!method method_alias?(node)
|
115
131
|
def_node_matcher :method_alias?, <<~PATTERN
|
116
|
-
(alias (sym $_name) sym)
|
132
|
+
(alias (sym $_name) (sym $_original_name))
|
117
133
|
PATTERN
|
118
134
|
|
119
135
|
def on_alias(node)
|
120
|
-
|
136
|
+
name, original_name = method_alias?(node)
|
137
|
+
return unless name && original_name
|
138
|
+
return if name == original_name
|
121
139
|
return if node.ancestors.any?(&:if_type?)
|
122
140
|
|
123
141
|
found_instance_method(node, name)
|
@@ -125,7 +143,7 @@ module RuboCop
|
|
125
143
|
|
126
144
|
# @!method alias_method?(node)
|
127
145
|
def_node_matcher :alias_method?, <<~PATTERN
|
128
|
-
(send nil? :alias_method (sym $_name)
|
146
|
+
(send nil? :alias_method (sym $_name) (sym $_original_name))
|
129
147
|
PATTERN
|
130
148
|
|
131
149
|
# @!method delegate_method?(node)
|
@@ -140,7 +158,10 @@ module RuboCop
|
|
140
158
|
def_node_matcher :sym_name, '(sym $_name)'
|
141
159
|
|
142
160
|
def on_send(node) # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
143
|
-
|
161
|
+
name, original_name = alias_method?(node)
|
162
|
+
|
163
|
+
if name && original_name
|
164
|
+
return if name == original_name
|
144
165
|
return if node.ancestors.any?(&:if_type?)
|
145
166
|
|
146
167
|
found_instance_method(node, name)
|
@@ -24,8 +24,6 @@ module RuboCop
|
|
24
24
|
|
25
25
|
MSG_REPEATED_ELEMENT = 'Duplicate element inside regexp character class'
|
26
26
|
|
27
|
-
OCTAL_DIGITS_AFTER_ESCAPE = 2
|
28
|
-
|
29
27
|
def on_regexp(node)
|
30
28
|
each_repeated_character_class_element_loc(node) do |loc|
|
31
29
|
add_offense(loc, message: MSG_REPEATED_ELEMENT) do |corrector|
|
@@ -40,9 +38,9 @@ module RuboCop
|
|
40
38
|
|
41
39
|
seen = Set.new
|
42
40
|
group_expressions(node, expr.expressions) do |group|
|
43
|
-
group_source = group.
|
41
|
+
group_source = group.to_s
|
44
42
|
|
45
|
-
yield
|
43
|
+
yield group.expression if seen.include?(group_source)
|
46
44
|
|
47
45
|
seen << group_source
|
48
46
|
end
|
@@ -52,40 +50,13 @@ module RuboCop
|
|
52
50
|
private
|
53
51
|
|
54
52
|
def group_expressions(node, expressions)
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
until expressions.empty?
|
59
|
-
# With we may need to compose a group of multiple expressions.
|
60
|
-
group = [expressions.shift]
|
61
|
-
next if within_interpolation?(node, group.first)
|
62
|
-
|
63
|
-
# With regexp_parser < 2.7 escaped octal sequences may be up to 3
|
64
|
-
# separate expressions ("\\0", "0", "1").
|
65
|
-
pop_octal_digits(group, expressions) if escaped_octal?(group.first.to_s)
|
66
|
-
|
67
|
-
yield(group)
|
68
|
-
end
|
69
|
-
end
|
70
|
-
|
71
|
-
def pop_octal_digits(current_child, expressions)
|
72
|
-
OCTAL_DIGITS_AFTER_ESCAPE.times do
|
73
|
-
next_child = expressions.first
|
74
|
-
break unless octal?(next_child.to_s)
|
53
|
+
expressions.each do |expression|
|
54
|
+
next if within_interpolation?(node, expression)
|
75
55
|
|
76
|
-
|
56
|
+
yield(expression)
|
77
57
|
end
|
78
58
|
end
|
79
59
|
|
80
|
-
def source_range(children)
|
81
|
-
return children.first.expression if children.size == 1
|
82
|
-
|
83
|
-
range_between(
|
84
|
-
children.first.expression.begin_pos,
|
85
|
-
children.last.expression.begin_pos + children.last.to_s.length
|
86
|
-
)
|
87
|
-
end
|
88
|
-
|
89
60
|
def skip_expression?(expr)
|
90
61
|
expr.type != :set || expr.token == :intersection
|
91
62
|
end
|
@@ -99,14 +70,6 @@ module RuboCop
|
|
99
70
|
interpolation_locs(node).any? { |il| il.overlaps?(parse_tree_child_loc) }
|
100
71
|
end
|
101
72
|
|
102
|
-
def escaped_octal?(string)
|
103
|
-
string.length == 2 && string[0] == '\\' && octal?(string[1])
|
104
|
-
end
|
105
|
-
|
106
|
-
def octal?(char)
|
107
|
-
('0'..'7').cover?(char)
|
108
|
-
end
|
109
|
-
|
110
73
|
def interpolation_locs(node)
|
111
74
|
@interpolation_locs ||= {}
|
112
75
|
|
@@ -19,7 +19,9 @@ module RuboCop
|
|
19
19
|
MSG = 'Empty interpolation detected.'
|
20
20
|
|
21
21
|
def on_interpolation(begin_node)
|
22
|
-
|
22
|
+
node_children = begin_node.children.dup
|
23
|
+
node_children.delete_if { |e| e.nil_type? || (e.basic_literal? && e.str_content&.empty?) }
|
24
|
+
return unless node_children.empty?
|
23
25
|
|
24
26
|
add_offense(begin_node) { |corrector| corrector.remove(begin_node) }
|
25
27
|
end
|
@@ -94,7 +94,7 @@ module RuboCop
|
|
94
94
|
when :float
|
95
95
|
true
|
96
96
|
when :send
|
97
|
-
|
97
|
+
float_send?(node)
|
98
98
|
when :begin
|
99
99
|
float?(node.children.first)
|
100
100
|
else
|
@@ -108,18 +108,18 @@ module RuboCop
|
|
108
108
|
(node.numeric_type? && node.value.zero?) || node.nil_type?
|
109
109
|
end
|
110
110
|
|
111
|
-
def
|
111
|
+
def float_send?(node)
|
112
112
|
if node.arithmetic_operation?
|
113
113
|
float?(node.receiver) || float?(node.first_argument)
|
114
114
|
elsif FLOAT_RETURNING_METHODS.include?(node.method_name)
|
115
115
|
true
|
116
116
|
elsif node.receiver&.float_type?
|
117
117
|
FLOAT_INSTANCE_METHODS.include?(node.method_name) ||
|
118
|
-
|
118
|
+
numeric_returning_method?(node)
|
119
119
|
end
|
120
120
|
end
|
121
121
|
|
122
|
-
def
|
122
|
+
def numeric_returning_method?(node)
|
123
123
|
return false unless node.receiver
|
124
124
|
|
125
125
|
case node.method_name
|
@@ -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
|
@@ -54,6 +54,18 @@ module RuboCop
|
|
54
54
|
end
|
55
55
|
end
|
56
56
|
|
57
|
+
def on_or(node)
|
58
|
+
return unless node.lhs.falsey_literal?
|
59
|
+
|
60
|
+
add_offense(node.lhs) do |corrector|
|
61
|
+
# Don't autocorrect `'foo' && return` because having `return` as
|
62
|
+
# the leftmost node can lead to a void value expression syntax error.
|
63
|
+
next if node.rhs.type?(:return, :break, :next)
|
64
|
+
|
65
|
+
corrector.replace(node, node.rhs.source)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
57
69
|
def on_if(node)
|
58
70
|
cond = condition(node)
|
59
71
|
|
@@ -123,7 +135,9 @@ module RuboCop
|
|
123
135
|
# rubocop:enable Metrics/AbcSize
|
124
136
|
|
125
137
|
def on_case(case_node)
|
126
|
-
if case_node.condition
|
138
|
+
if (cond = case_node.condition)
|
139
|
+
return if !cond.falsey_literal? && !cond.truthy_literal?
|
140
|
+
|
127
141
|
check_case(case_node)
|
128
142
|
else
|
129
143
|
case_node.when_branches.each do |when_node|
|
@@ -228,7 +242,7 @@ module RuboCop
|
|
228
242
|
)
|
229
243
|
end
|
230
244
|
|
231
|
-
def condition_evaluation(node, cond)
|
245
|
+
def condition_evaluation?(node, cond)
|
232
246
|
if node.unless?
|
233
247
|
cond.falsey_literal?
|
234
248
|
else
|
@@ -238,32 +252,24 @@ module RuboCop
|
|
238
252
|
|
239
253
|
# rubocop:disable Metrics/AbcSize, Metrics/MethodLength, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
240
254
|
def correct_if_node(node, cond)
|
241
|
-
result = condition_evaluation(node, cond)
|
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
|
255
|
+
result = condition_evaluation?(node, cond)
|
256
|
+
|
257
|
+
new_node = if node.elsif? && result
|
258
|
+
"else\n #{range_with_comments(node.if_branch).source}"
|
259
|
+
elsif node.elsif? && !result
|
260
|
+
"else\n #{node.else_branch.source}"
|
261
|
+
elsif node.if_branch && result
|
262
|
+
node.if_branch.source
|
263
|
+
elsif node.elsif_conditional?
|
264
|
+
"#{node.else_branch.source.sub('elsif', 'if')}\nend"
|
265
|
+
elsif node.else? || node.ternary?
|
266
|
+
node.else_branch.source
|
267
|
+
else
|
268
|
+
'' # Equivalent to removing the node
|
269
|
+
end
|
270
|
+
|
271
|
+
add_offense(cond) do |corrector|
|
272
|
+
corrector.replace(node, new_node)
|
267
273
|
end
|
268
274
|
end
|
269
275
|
# rubocop:enable Metrics/AbcSize, Metrics/MethodLength, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
@@ -52,10 +52,9 @@ module RuboCop
|
|
52
52
|
each_missing_enable do |cop, line_range|
|
53
53
|
next if acceptable_range?(cop, line_range)
|
54
54
|
|
55
|
-
range = source_range(processed_source.buffer, line_range.min, 0..0)
|
56
55
|
comment = processed_source.comment_at_line(line_range.begin)
|
57
56
|
|
58
|
-
add_offense(
|
57
|
+
add_offense(comment, message: message(cop, comment))
|
59
58
|
end
|
60
59
|
end
|
61
60
|
|
@@ -73,7 +73,7 @@ module RuboCop
|
|
73
73
|
end
|
74
74
|
|
75
75
|
def redundant_group?(expr)
|
76
|
-
expr.is?(:passive, :group) && expr.
|
76
|
+
expr.is?(:passive, :group) && expr.one? { |child| child.type != :free_space }
|
77
77
|
end
|
78
78
|
|
79
79
|
def redundantly_quantifiable?(node)
|
@@ -20,6 +20,15 @@ module RuboCop
|
|
20
20
|
# In the example below, the safe navigation operator (`&.`) is unnecessary
|
21
21
|
# because `NilClass` has methods like `respond_to?` and `is_a?`.
|
22
22
|
#
|
23
|
+
# The `InferNonNilReceiver` option specifies whether to look into previous code
|
24
|
+
# paths to infer if the receiver can't be nil. This check is unsafe because the receiver
|
25
|
+
# can be redefined between the safe navigation call and previous regular method call.
|
26
|
+
# It does the inference only in the current scope, e.g. within the same method definition etc.
|
27
|
+
#
|
28
|
+
# The `AdditionalNilMethods` option specifies additional custom methods which are
|
29
|
+
# defined on `NilClass`. When `InferNonNilReceiver` is set, they are used to determine
|
30
|
+
# whether the receiver can be nil.
|
31
|
+
#
|
23
32
|
# @safety
|
24
33
|
# This cop is unsafe, because autocorrection can change the return type of
|
25
34
|
# the expression. An offending expression that previously could return `nil`
|
@@ -33,6 +42,20 @@ module RuboCop
|
|
33
42
|
# CamelCaseConst.do_something
|
34
43
|
#
|
35
44
|
# # bad
|
45
|
+
# foo.to_s&.strip
|
46
|
+
# foo.to_i&.zero?
|
47
|
+
# foo.to_f&.zero?
|
48
|
+
# foo.to_a&.size
|
49
|
+
# foo.to_h&.size
|
50
|
+
#
|
51
|
+
# # good
|
52
|
+
# foo.to_s.strip
|
53
|
+
# foo.to_i.zero?
|
54
|
+
# foo.to_f.zero?
|
55
|
+
# foo.to_a.size
|
56
|
+
# foo.to_h.size
|
57
|
+
#
|
58
|
+
# # bad
|
36
59
|
# do_something if attrs&.respond_to?(:[])
|
37
60
|
#
|
38
61
|
# # good
|
@@ -81,17 +104,59 @@ module RuboCop
|
|
81
104
|
# do_something if attrs.nil_safe_method(:[])
|
82
105
|
# do_something if attrs&.not_nil_safe_method(:[])
|
83
106
|
#
|
107
|
+
# @example InferNonNilReceiver: false (default)
|
108
|
+
# # good
|
109
|
+
# foo.bar
|
110
|
+
# foo&.baz
|
111
|
+
#
|
112
|
+
# @example InferNonNilReceiver: true
|
113
|
+
# # bad
|
114
|
+
# foo.bar
|
115
|
+
# foo&.baz # would raise on previous line if `foo` is nil
|
116
|
+
#
|
117
|
+
# # good
|
118
|
+
# foo.bar
|
119
|
+
# foo.baz
|
120
|
+
#
|
121
|
+
# # bad
|
122
|
+
# if foo.condition?
|
123
|
+
# foo&.bar
|
124
|
+
# end
|
125
|
+
#
|
126
|
+
# # good
|
127
|
+
# if foo.condition?
|
128
|
+
# foo.bar
|
129
|
+
# end
|
130
|
+
#
|
131
|
+
# # good (different scopes)
|
132
|
+
# def method1
|
133
|
+
# foo.bar
|
134
|
+
# end
|
135
|
+
#
|
136
|
+
# def method2
|
137
|
+
# foo&.bar
|
138
|
+
# end
|
139
|
+
#
|
140
|
+
# @example AdditionalNilMethods: [present?]
|
141
|
+
# # good
|
142
|
+
# foo.present?
|
143
|
+
# foo&.bar
|
144
|
+
#
|
84
145
|
class RedundantSafeNavigation < Base
|
85
146
|
include AllowedMethods
|
86
147
|
extend AutoCorrector
|
87
148
|
|
88
149
|
MSG = 'Redundant safe navigation detected, use `.` instead.'
|
89
150
|
MSG_LITERAL = 'Redundant safe navigation with default literal detected.'
|
151
|
+
MSG_NON_NIL = 'Redundant safe navigation on non-nil receiver (detected by analyzing ' \
|
152
|
+
'previous code/method invocations).'
|
90
153
|
|
91
154
|
NIL_SPECIFIC_METHODS = (nil.methods - Object.new.methods).to_set.freeze
|
92
155
|
|
93
156
|
SNAKE_CASE = /\A[[:digit:][:upper:]_]+\z/.freeze
|
94
157
|
|
158
|
+
GUARANTEED_INSTANCE_METHODS = %i[to_s to_i to_f to_a to_h].freeze
|
159
|
+
|
95
160
|
# @!method respond_to_nil_specific_method?(node)
|
96
161
|
def_node_matcher :respond_to_nil_specific_method?, <<~PATTERN
|
97
162
|
(csend _ :respond_to? (sym %NIL_SPECIFIC_METHODS))
|
@@ -111,15 +176,27 @@ module RuboCop
|
|
111
176
|
|
112
177
|
# rubocop:disable Metrics/AbcSize
|
113
178
|
def on_csend(node)
|
179
|
+
range = node.loc.dot
|
180
|
+
|
181
|
+
if infer_non_nil_receiver?
|
182
|
+
checker = Lint::Utils::NilReceiverChecker.new(node.receiver, additional_nil_methods)
|
183
|
+
|
184
|
+
if checker.cant_be_nil?
|
185
|
+
add_offense(range, message: MSG_NON_NIL) { |corrector| corrector.replace(range, '.') }
|
186
|
+
return
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
114
190
|
unless assume_receiver_instance_exists?(node.receiver)
|
115
|
-
return
|
191
|
+
return if !guaranteed_instance?(node.receiver) && !check?(node)
|
116
192
|
return if respond_to_nil_specific_method?(node)
|
117
193
|
end
|
118
194
|
|
119
|
-
range = node.loc.dot
|
120
195
|
add_offense(range) { |corrector| corrector.replace(range, '.') }
|
121
196
|
end
|
197
|
+
# rubocop:enable Metrics/AbcSize
|
122
198
|
|
199
|
+
# rubocop:disable Metrics/AbcSize
|
123
200
|
def on_or(node)
|
124
201
|
conversion_with_default?(node) do |send_node|
|
125
202
|
range = send_node.loc.dot.begin.join(node.source_range.end)
|
@@ -142,7 +219,20 @@ module RuboCop
|
|
142
219
|
receiver.self_type? || (receiver.literal? && !receiver.nil_type?)
|
143
220
|
end
|
144
221
|
|
222
|
+
def guaranteed_instance?(node)
|
223
|
+
receiver = if node.any_block_type?
|
224
|
+
node.send_node
|
225
|
+
else
|
226
|
+
node
|
227
|
+
end
|
228
|
+
return false unless receiver.send_type?
|
229
|
+
|
230
|
+
GUARANTEED_INSTANCE_METHODS.include?(receiver.method_name)
|
231
|
+
end
|
232
|
+
|
145
233
|
def check?(node)
|
234
|
+
return false unless allowed_method?(node.method_name)
|
235
|
+
|
146
236
|
parent = node.parent
|
147
237
|
return false unless parent
|
148
238
|
|
@@ -154,6 +244,15 @@ module RuboCop
|
|
154
244
|
def condition?(parent, node)
|
155
245
|
(parent.conditional? || parent.post_condition_loop?) && parent.condition == node
|
156
246
|
end
|
247
|
+
|
248
|
+
def infer_non_nil_receiver?
|
249
|
+
cop_config['InferNonNilReceiver']
|
250
|
+
end
|
251
|
+
|
252
|
+
def additional_nil_methods
|
253
|
+
@additional_nil_methods ||=
|
254
|
+
Array(cop_config.fetch('AdditionalNilMethods', []).map(&:to_sym))
|
255
|
+
end
|
157
256
|
end
|
158
257
|
end
|
159
258
|
end
|
@@ -185,9 +185,9 @@ module RuboCop
|
|
185
185
|
(hash (pair (sym :exception) false))
|
186
186
|
PATTERN
|
187
187
|
|
188
|
-
# rubocop:disable Metrics/AbcSize
|
188
|
+
# rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity
|
189
189
|
def on_send(node)
|
190
|
-
return if hash_or_set_with_block?(node)
|
190
|
+
return if node.arguments.any? || hash_or_set_with_block?(node)
|
191
191
|
|
192
192
|
receiver = find_receiver(node)
|
193
193
|
return unless literal_receiver?(node, receiver) ||
|
@@ -198,10 +198,10 @@ module RuboCop
|
|
198
198
|
message = format(MSG, method: node.method_name)
|
199
199
|
|
200
200
|
add_offense(node.loc.selector, message: message) do |corrector|
|
201
|
-
corrector.remove(node.loc.dot.join(node.loc.selector))
|
201
|
+
corrector.remove(node.loc.dot.join(node.loc.end || node.loc.selector))
|
202
202
|
end
|
203
203
|
end
|
204
|
-
# rubocop:enable Metrics/AbcSize
|
204
|
+
# rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity
|
205
205
|
alias on_csend on_send
|
206
206
|
|
207
207
|
private
|
@@ -43,7 +43,7 @@ module RuboCop
|
|
43
43
|
def on_irange(node)
|
44
44
|
return if node.parent&.begin_type?
|
45
45
|
return unless node.begin && node.end
|
46
|
-
return if same_line?(node.
|
46
|
+
return if same_line?(node.loc.operator, node.end)
|
47
47
|
|
48
48
|
message = format(MSG, range: "#{node.begin.source}#{node.loc.operator.source}")
|
49
49
|
|
@@ -97,7 +97,7 @@ module RuboCop
|
|
97
97
|
end
|
98
98
|
|
99
99
|
def require_parentheses?(send_node)
|
100
|
-
return true if
|
100
|
+
return true if operator_inside_collection_literal?(send_node)
|
101
101
|
return false unless send_node.comparison_method?
|
102
102
|
return false unless (node = send_node.parent)
|
103
103
|
|
@@ -105,10 +105,10 @@ module RuboCop
|
|
105
105
|
(node.respond_to?(:comparison_method?) && node.comparison_method?)
|
106
106
|
end
|
107
107
|
|
108
|
-
def
|
109
|
-
# If an operator call (without a dot) is inside a hash, it needs
|
108
|
+
def operator_inside_collection_literal?(send_node)
|
109
|
+
# If an operator call (without a dot) is inside an array or a hash, it needs
|
110
110
|
# to be parenthesized when converted to safe navigation.
|
111
|
-
send_node.parent&.
|
111
|
+
send_node.parent&.type?(:array, :pair) && !send_node.loc.dot
|
112
112
|
end
|
113
113
|
end
|
114
114
|
end
|