rubocop 1.66.1 → 1.68.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 +1 -1
- data/config/default.yml +55 -6
- data/config/internal_affairs.yml +11 -0
- data/lib/rubocop/cached_data.rb +12 -4
- data/lib/rubocop/cli/command/auto_generate_config.rb +6 -7
- data/lib/rubocop/cli/command/execute_runner.rb +1 -1
- data/lib/rubocop/cli/command/lsp.rb +2 -2
- data/lib/rubocop/cli/command/version.rb +2 -2
- data/lib/rubocop/config_loader_resolver.rb +3 -3
- data/lib/rubocop/config_validator.rb +2 -1
- data/lib/rubocop/cop/autocorrect_logic.rb +22 -2
- data/lib/rubocop/cop/base.rb +6 -2
- data/lib/rubocop/cop/bundler/gem_version.rb +1 -0
- data/lib/rubocop/cop/cop.rb +8 -0
- data/lib/rubocop/cop/correctors/alignment_corrector.rb +1 -12
- data/lib/rubocop/cop/correctors/parentheses_corrector.rb +1 -1
- data/lib/rubocop/cop/correctors/percent_literal_corrector.rb +10 -0
- data/lib/rubocop/cop/internal_affairs/cop_description.rb +0 -4
- data/lib/rubocop/cop/internal_affairs/redundant_message_argument.rb +6 -21
- data/lib/rubocop/cop/internal_affairs/redundant_source_range.rb +8 -1
- data/lib/rubocop/cop/internal_affairs/useless_message_assertion.rb +0 -5
- data/lib/rubocop/cop/internal_affairs.rb +16 -0
- data/lib/rubocop/cop/layout/access_modifier_indentation.rb +5 -1
- data/lib/rubocop/cop/layout/def_end_alignment.rb +1 -1
- data/lib/rubocop/cop/layout/empty_line_after_guard_clause.rb +1 -1
- data/lib/rubocop/cop/layout/first_method_argument_line_break.rb +8 -0
- data/lib/rubocop/cop/layout/indentation_width.rb +4 -5
- data/lib/rubocop/cop/layout/leading_comment_space.rb +56 -1
- data/lib/rubocop/cop/layout/space_before_brackets.rb +5 -5
- data/lib/rubocop/cop/layout/space_inside_block_braces.rb +4 -0
- data/lib/rubocop/cop/lint/ambiguous_range.rb +4 -1
- data/lib/rubocop/cop/lint/big_decimal_new.rb +4 -7
- data/lib/rubocop/cop/lint/boolean_symbol.rb +1 -1
- data/lib/rubocop/cop/lint/duplicate_branch.rb +39 -4
- data/lib/rubocop/cop/lint/duplicate_set_element.rb +74 -0
- data/lib/rubocop/cop/lint/ensure_return.rb +0 -3
- data/lib/rubocop/cop/lint/erb_new_arguments.rb +1 -1
- data/lib/rubocop/cop/lint/float_comparison.rb +1 -1
- data/lib/rubocop/cop/lint/implicit_string_concatenation.rb +10 -4
- data/lib/rubocop/cop/lint/it_without_arguments_in_block.rb +5 -14
- data/lib/rubocop/cop/lint/literal_in_interpolation.rb +25 -2
- data/lib/rubocop/cop/lint/non_atomic_file_operation.rb +7 -0
- data/lib/rubocop/cop/lint/parentheses_as_grouped_expression.rb +5 -6
- data/lib/rubocop/cop/lint/redundant_safe_navigation.rb +1 -1
- data/lib/rubocop/cop/lint/safe_navigation_chain.rb +9 -0
- data/lib/rubocop/cop/lint/safe_navigation_consistency.rb +107 -41
- data/lib/rubocop/cop/lint/symbol_conversion.rb +1 -1
- data/lib/rubocop/cop/lint/unescaped_bracket_in_regexp.rb +88 -0
- data/lib/rubocop/cop/lint/uri_regexp.rb +25 -7
- data/lib/rubocop/cop/metrics/cyclomatic_complexity.rb +4 -1
- data/lib/rubocop/cop/mixin/check_line_breakable.rb +10 -0
- data/lib/rubocop/cop/mixin/endless_method_rewriter.rb +24 -0
- data/lib/rubocop/cop/mixin/frozen_string_literal.rb +3 -1
- data/lib/rubocop/cop/mixin/percent_array.rb +1 -1
- data/lib/rubocop/cop/mixin/statement_modifier.rb +3 -2
- data/lib/rubocop/cop/naming/block_forwarding.rb +1 -1
- data/lib/rubocop/cop/naming/inclusive_language.rb +12 -3
- data/lib/rubocop/cop/naming/predicate_name.rb +1 -1
- data/lib/rubocop/cop/offense.rb +4 -5
- data/lib/rubocop/cop/style/access_modifier_declarations.rb +12 -2
- data/lib/rubocop/cop/style/accessor_grouping.rb +10 -2
- data/lib/rubocop/cop/style/ambiguous_endless_method_definition.rb +79 -0
- data/lib/rubocop/cop/style/arguments_forwarding.rb +46 -6
- data/lib/rubocop/cop/style/bitwise_predicate.rb +100 -0
- data/lib/rubocop/cop/style/block_delimiters.rb +31 -3
- data/lib/rubocop/cop/style/collection_compact.rb +10 -10
- data/lib/rubocop/cop/style/combinable_defined.rb +115 -0
- data/lib/rubocop/cop/style/combinable_loops.rb +7 -0
- data/lib/rubocop/cop/style/commented_keyword.rb +7 -1
- data/lib/rubocop/cop/style/conditional_assignment.rb +1 -1
- data/lib/rubocop/cop/style/data_inheritance.rb +1 -1
- data/lib/rubocop/cop/style/endless_method.rb +1 -14
- data/lib/rubocop/cop/style/eval_with_location.rb +1 -1
- data/lib/rubocop/cop/style/guard_clause.rb +15 -2
- data/lib/rubocop/cop/style/hash_each_methods.rb +6 -0
- data/lib/rubocop/cop/style/hash_syntax.rb +2 -2
- data/lib/rubocop/cop/style/if_inside_else.rb +1 -1
- data/lib/rubocop/cop/style/if_with_semicolon.rb +7 -3
- data/lib/rubocop/cop/style/keyword_arguments_merging.rb +67 -0
- data/lib/rubocop/cop/style/lambda.rb +1 -1
- data/lib/rubocop/cop/style/map_into_array.rb +59 -8
- data/lib/rubocop/cop/style/method_call_with_args_parentheses/omit_parentheses.rb +12 -7
- data/lib/rubocop/cop/style/multiline_memoization.rb +1 -1
- data/lib/rubocop/cop/style/multiple_comparison.rb +28 -39
- data/lib/rubocop/cop/style/nested_modifier.rb +1 -1
- data/lib/rubocop/cop/style/nested_parenthesized_calls.rb +1 -1
- data/lib/rubocop/cop/style/one_line_conditional.rb +4 -0
- data/lib/rubocop/cop/style/operator_method_call.rb +25 -6
- data/lib/rubocop/cop/style/redundant_begin.rb +4 -0
- data/lib/rubocop/cop/style/redundant_condition.rb +1 -1
- data/lib/rubocop/cop/style/redundant_line_continuation.rb +23 -5
- data/lib/rubocop/cop/style/redundant_parentheses.rb +9 -11
- data/lib/rubocop/cop/style/require_order.rb +1 -1
- data/lib/rubocop/cop/style/rescue_modifier.rb +13 -1
- data/lib/rubocop/cop/style/return_nil_in_predicate_method_definition.rb +54 -12
- data/lib/rubocop/cop/style/safe_navigation.rb +104 -50
- data/lib/rubocop/cop/style/safe_navigation_chain_length.rb +52 -0
- data/lib/rubocop/cop/style/select_by_regexp.rb +9 -6
- data/lib/rubocop/cop/style/semicolon.rb +1 -1
- data/lib/rubocop/cop/style/struct_inheritance.rb +1 -1
- data/lib/rubocop/cop/style/ternary_parentheses.rb +26 -5
- data/lib/rubocop/cop/style/trivial_accessors.rb +1 -1
- data/lib/rubocop/cop/team.rb +8 -1
- data/lib/rubocop/cop/util.rb +1 -1
- data/lib/rubocop/cop/variable_force/assignment.rb +18 -3
- data/lib/rubocop/cop/variable_force/branch.rb +1 -1
- data/lib/rubocop/cop/variable_force/variable.rb +5 -1
- data/lib/rubocop/cop/variable_force/variable_table.rb +2 -2
- data/lib/rubocop/cops_documentation_generator.rb +81 -40
- data/lib/rubocop/file_finder.rb +9 -4
- data/lib/rubocop/formatter/disabled_config_formatter.rb +1 -1
- data/lib/rubocop/lsp/runtime.rb +2 -0
- data/lib/rubocop/lsp/server.rb +0 -1
- data/lib/rubocop/rspec/expect_offense.rb +1 -0
- data/lib/rubocop/runner.rb +17 -6
- data/lib/rubocop/server/cache.rb +6 -1
- data/lib/rubocop/server/core.rb +1 -0
- data/lib/rubocop/target_ruby.rb +13 -13
- data/lib/rubocop/version.rb +28 -7
- data/lib/rubocop/yaml_duplication_checker.rb +20 -27
- data/lib/rubocop.rb +10 -0
- metadata +13 -4
@@ -69,6 +69,8 @@ module RuboCop
|
|
69
69
|
extend AutoCorrector
|
70
70
|
|
71
71
|
MSG = 'Redundant line continuation.'
|
72
|
+
LINE_CONTINUATION = "\\\n"
|
73
|
+
LINE_CONTINUATION_PATTERN = /(\\\n)/.freeze
|
72
74
|
ALLOWED_STRING_TOKENS = %i[tSTRING tSTRING_CONTENT].freeze
|
73
75
|
ARGUMENT_TYPES = %i[
|
74
76
|
kFALSE kNIL kSELF kTRUE tCONSTANT tCVAR tFLOAT tGVAR tIDENTIFIER tINTEGER tIVAR
|
@@ -79,7 +81,7 @@ module RuboCop
|
|
79
81
|
def on_new_investigation
|
80
82
|
return unless processed_source.ast
|
81
83
|
|
82
|
-
each_match_range(processed_source.ast.source_range,
|
84
|
+
each_match_range(processed_source.ast.source_range, LINE_CONTINUATION_PATTERN) do |range|
|
83
85
|
next if require_line_continuation?(range)
|
84
86
|
next unless redundant_line_continuation?(range)
|
85
87
|
|
@@ -87,6 +89,8 @@ module RuboCop
|
|
87
89
|
corrector.remove_leading(range, 1)
|
88
90
|
end
|
89
91
|
end
|
92
|
+
|
93
|
+
inspect_eof_line_continuation
|
90
94
|
end
|
91
95
|
|
92
96
|
private
|
@@ -122,13 +126,27 @@ module RuboCop
|
|
122
126
|
end
|
123
127
|
|
124
128
|
def redundant_line_continuation?(range)
|
125
|
-
return true unless (node = find_node_for_line(range.
|
129
|
+
return true unless (node = find_node_for_line(range.last_line))
|
126
130
|
return false if argument_newline?(node)
|
127
131
|
|
128
|
-
source = node.
|
132
|
+
source = node.source
|
133
|
+
while (node = node.parent)
|
134
|
+
source = node.source
|
135
|
+
end
|
129
136
|
parse(source.gsub("\\\n", "\n")).valid_syntax?
|
130
137
|
end
|
131
138
|
|
139
|
+
def inspect_eof_line_continuation
|
140
|
+
return unless processed_source.raw_source.end_with?(LINE_CONTINUATION)
|
141
|
+
|
142
|
+
rindex = processed_source.raw_source.rindex(LINE_CONTINUATION)
|
143
|
+
line_continuation_range = range_between(rindex, rindex + 1)
|
144
|
+
|
145
|
+
add_offense(line_continuation_range) do |corrector|
|
146
|
+
corrector.remove_trailing(line_continuation_range, 1)
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
132
150
|
def inside_string_literal?(range, token)
|
133
151
|
ALLOWED_STRING_TOKENS.include?(token.type) && token.pos.overlaps?(range)
|
134
152
|
end
|
@@ -160,9 +178,9 @@ module RuboCop
|
|
160
178
|
end
|
161
179
|
# rubocop:enable Metrics/AbcSize
|
162
180
|
|
163
|
-
def find_node_for_line(
|
181
|
+
def find_node_for_line(last_line)
|
164
182
|
processed_source.ast.each_node do |node|
|
165
|
-
return node if
|
183
|
+
return node if node.respond_to?(:expression) && node.expression&.last_line == last_line
|
166
184
|
end
|
167
185
|
end
|
168
186
|
|
@@ -31,9 +31,6 @@ module RuboCop
|
|
31
31
|
# @!method allowed_pin_operator?(node)
|
32
32
|
def_node_matcher :allowed_pin_operator?, '^(pin (begin !{lvar ivar cvar gvar}))'
|
33
33
|
|
34
|
-
# @!method arg_in_call_with_block?(node)
|
35
|
-
def_node_matcher :arg_in_call_with_block?, '^^(block (send _ _ equal?(%0) ...) ...)'
|
36
|
-
|
37
34
|
def on_begin(node)
|
38
35
|
return if !parentheses?(node) || parens_allowed?(node) || ignore_syntax?(node)
|
39
36
|
|
@@ -59,7 +56,6 @@ module RuboCop
|
|
59
56
|
|
60
57
|
def allowed_expression?(node)
|
61
58
|
allowed_ancestor?(node) ||
|
62
|
-
allowed_method_call?(node) ||
|
63
59
|
allowed_multiple_expression?(node) ||
|
64
60
|
allowed_ternary?(node) ||
|
65
61
|
node.parent&.range_type?
|
@@ -70,11 +66,6 @@ module RuboCop
|
|
70
66
|
keyword_ancestor?(node) && parens_required?(node)
|
71
67
|
end
|
72
68
|
|
73
|
-
def allowed_method_call?(node)
|
74
|
-
# Don't flag `method (arg) { }`
|
75
|
-
arg_in_call_with_block?(node) && !parentheses?(node.parent)
|
76
|
-
end
|
77
|
-
|
78
69
|
def allowed_multiple_expression?(node)
|
79
70
|
return false if node.children.one?
|
80
71
|
|
@@ -160,7 +151,7 @@ module RuboCop
|
|
160
151
|
return if node.semantic_operator? && begin_node.parent
|
161
152
|
return if node.multiline? && allow_in_multiline_conditions?
|
162
153
|
return if ALLOWED_NODE_TYPES.include?(begin_node.parent&.type)
|
163
|
-
return if begin_node.parent&.if_type? && begin_node.parent
|
154
|
+
return if begin_node.parent&.if_type? && begin_node.parent.ternary?
|
164
155
|
|
165
156
|
'a logical expression'
|
166
157
|
elsif node.respond_to?(:comparison_method?) && node.comparison_method?
|
@@ -185,7 +176,8 @@ module RuboCop
|
|
185
176
|
return check_unary(begin_node, node) if node.unary_operation?
|
186
177
|
|
187
178
|
return unless method_call_with_redundant_parentheses?(node)
|
188
|
-
return if call_chain_starts_with_int?(begin_node, node)
|
179
|
+
return if call_chain_starts_with_int?(begin_node, node) ||
|
180
|
+
do_end_block_in_method_chain?(begin_node, node)
|
189
181
|
|
190
182
|
offense(begin_node, 'a method call')
|
191
183
|
end
|
@@ -285,6 +277,12 @@ module RuboCop
|
|
285
277
|
recv&.int_type? && (parent = begin_node.parent) &&
|
286
278
|
parent.send_type? && (parent.method?(:-@) || parent.method?(:+@))
|
287
279
|
end
|
280
|
+
|
281
|
+
def do_end_block_in_method_chain?(begin_node, node)
|
282
|
+
return false unless (block = node.each_descendant(:block, :numblock).first)
|
283
|
+
|
284
|
+
block.keywords? && begin_node.each_ancestor(:send, :csend).any?
|
285
|
+
end
|
288
286
|
end
|
289
287
|
end
|
290
288
|
end
|
@@ -103,7 +103,7 @@ module RuboCop
|
|
103
103
|
next unless sibling.is_a?(AST::Node)
|
104
104
|
|
105
105
|
sibling = sibling_node(sibling)
|
106
|
-
break unless sibling&.send_type? && sibling
|
106
|
+
break unless sibling&.send_type? && sibling.method?(node.method_name)
|
107
107
|
break unless sibling.arguments? && !sibling.receiver
|
108
108
|
break unless in_same_section?(sibling, node)
|
109
109
|
break unless node.first_argument.str_type? && sibling.first_argument.str_type?
|
@@ -75,7 +75,7 @@ module RuboCop
|
|
75
75
|
|
76
76
|
corrector.remove(range_between(operation.source_range.end_pos, node.source_range.end_pos))
|
77
77
|
corrector.insert_before(operation, "begin\n#{node_indentation}")
|
78
|
-
corrector.insert_after(operation, <<~RESCUE_CLAUSE.chop)
|
78
|
+
corrector.insert_after(heredoc_end(operation) || operation, <<~RESCUE_CLAUSE.chop)
|
79
79
|
|
80
80
|
#{node_offset}rescue
|
81
81
|
#{node_indentation}#{rescue_args.source}
|
@@ -92,6 +92,18 @@ module RuboCop
|
|
92
92
|
end
|
93
93
|
[node_indentation, node_offset]
|
94
94
|
end
|
95
|
+
|
96
|
+
def heredoc_end(node)
|
97
|
+
return unless node.call_type?
|
98
|
+
|
99
|
+
heredoc = node.arguments.reverse.find do |argument|
|
100
|
+
argument.respond_to?(:heredoc?) && argument.heredoc?
|
101
|
+
end
|
102
|
+
|
103
|
+
return unless heredoc
|
104
|
+
|
105
|
+
heredoc.loc.heredoc_end
|
106
|
+
end
|
95
107
|
end
|
96
108
|
end
|
97
109
|
end
|
@@ -3,7 +3,8 @@
|
|
3
3
|
module RuboCop
|
4
4
|
module Cop
|
5
5
|
module Style
|
6
|
-
# Checks
|
6
|
+
# Checks for predicate method definitions that return `nil`.
|
7
|
+
# A predicate method should only return a boolean value.
|
7
8
|
#
|
8
9
|
# @safety
|
9
10
|
# Autocorrection is marked as unsafe because the change of the return value
|
@@ -31,6 +32,24 @@ module RuboCop
|
|
31
32
|
# do_something?
|
32
33
|
# end
|
33
34
|
#
|
35
|
+
# # bad
|
36
|
+
# def foo?
|
37
|
+
# if condition
|
38
|
+
# nil
|
39
|
+
# else
|
40
|
+
# true
|
41
|
+
# end
|
42
|
+
# end
|
43
|
+
#
|
44
|
+
# # good
|
45
|
+
# def foo?
|
46
|
+
# if condition
|
47
|
+
# false
|
48
|
+
# else
|
49
|
+
# true
|
50
|
+
# end
|
51
|
+
# end
|
52
|
+
#
|
34
53
|
# @example AllowedMethods: ['foo?']
|
35
54
|
# # good
|
36
55
|
# def foo?
|
@@ -64,24 +83,25 @@ module RuboCop
|
|
64
83
|
return if allowed_method?(node.method_name) || matches_allowed_pattern?(node.method_name)
|
65
84
|
return unless (body = node.body)
|
66
85
|
|
67
|
-
body.each_descendant(:return)
|
68
|
-
register_offense(return_node, 'return false') if return_nil?(return_node)
|
69
|
-
end
|
70
|
-
|
71
|
-
return unless (nil_node = nil_node_at_the_end_of_method_body(body))
|
86
|
+
body.each_descendant(:return) { |return_node| handle_return(return_node) }
|
72
87
|
|
73
|
-
|
88
|
+
handle_implicit_return_values(body)
|
74
89
|
end
|
75
90
|
alias on_defs on_def
|
76
91
|
|
77
92
|
private
|
78
93
|
|
79
|
-
def
|
80
|
-
return
|
81
|
-
return
|
82
|
-
return unless
|
94
|
+
def last_node_of_type(node, type)
|
95
|
+
return unless node
|
96
|
+
return node if node_type?(node, type)
|
97
|
+
return unless node.begin_type?
|
98
|
+
return unless (last_child = node.children.last)
|
99
|
+
|
100
|
+
last_child if last_child.is_a?(AST::Node) && node_type?(last_child, type)
|
101
|
+
end
|
83
102
|
|
84
|
-
|
103
|
+
def node_type?(node, type)
|
104
|
+
node.type == type.to_sym
|
85
105
|
end
|
86
106
|
|
87
107
|
def register_offense(offense_node, replacement)
|
@@ -89,6 +109,28 @@ module RuboCop
|
|
89
109
|
corrector.replace(offense_node, replacement)
|
90
110
|
end
|
91
111
|
end
|
112
|
+
|
113
|
+
def handle_implicit_return_values(node)
|
114
|
+
handle_if(last_node_of_type(node, :if))
|
115
|
+
handle_nil(last_node_of_type(node, :nil))
|
116
|
+
end
|
117
|
+
|
118
|
+
def handle_return(return_node)
|
119
|
+
register_offense(return_node, 'return false') if return_nil?(return_node)
|
120
|
+
end
|
121
|
+
|
122
|
+
def handle_nil(nil_node)
|
123
|
+
return unless nil_node
|
124
|
+
|
125
|
+
register_offense(nil_node, 'false')
|
126
|
+
end
|
127
|
+
|
128
|
+
def handle_if(if_node)
|
129
|
+
return unless if_node
|
130
|
+
|
131
|
+
handle_implicit_return_values(if_node.if_branch)
|
132
|
+
handle_implicit_return_values(if_node.else_branch)
|
133
|
+
end
|
92
134
|
end
|
93
135
|
end
|
94
136
|
end
|
@@ -21,6 +21,11 @@ module RuboCop
|
|
21
21
|
# We have limited the cop to not register an offense for method chains
|
22
22
|
# that exceed this option's value.
|
23
23
|
#
|
24
|
+
# NOTE: This cop will recognize offenses but not autocorrect code when the
|
25
|
+
# right hand side (RHS) of the `&&` statement is an `||` statement
|
26
|
+
# (eg. `foo && (foo.bar? || foo.baz?)`). It can be corrected
|
27
|
+
# manually by removing the `foo &&` and adding `&.` to each `foo` on the RHS.
|
28
|
+
#
|
24
29
|
# @safety
|
25
30
|
# Autocorrection is unsafe because if a value is `false`, the resulting
|
26
31
|
# code will have different behavior or raise an error.
|
@@ -81,7 +86,7 @@ module RuboCop
|
|
81
86
|
# foo.baz = bar if foo
|
82
87
|
# foo.baz + bar if foo
|
83
88
|
# foo.bar > 2 if foo
|
84
|
-
class SafeNavigation < Base
|
89
|
+
class SafeNavigation < Base # rubocop:disable Metrics/ClassLength
|
85
90
|
include NilMethods
|
86
91
|
include RangeHelp
|
87
92
|
extend AutoCorrector
|
@@ -121,50 +126,122 @@ module RuboCop
|
|
121
126
|
}
|
122
127
|
PATTERN
|
123
128
|
|
129
|
+
# @!method and_with_rhs_or?(node)
|
130
|
+
def_node_matcher :and_with_rhs_or?, '(and _ {or (begin or)})'
|
131
|
+
|
124
132
|
# @!method not_nil_check?(node)
|
125
133
|
def_node_matcher :not_nil_check?, '(send (send $_ :nil?) :!)'
|
126
134
|
|
135
|
+
# @!method and_inside_begin?(node)
|
136
|
+
def_node_matcher :and_inside_begin?, '`(begin and ...)'
|
137
|
+
|
138
|
+
# @!method strip_begin(node)
|
139
|
+
def_node_matcher :strip_begin, '{ (begin $!begin) $!(begin) }'
|
140
|
+
|
127
141
|
def on_if(node)
|
128
142
|
return if allowed_if_condition?(node)
|
129
143
|
|
130
|
-
|
144
|
+
checked_variable, receiver, method_chain, _method = extract_parts_from_if(node)
|
145
|
+
return unless offending_node?(node, checked_variable, method_chain, receiver)
|
146
|
+
|
147
|
+
body = extract_if_body(node)
|
148
|
+
method_call = receiver.parent
|
149
|
+
|
150
|
+
removal_ranges = [begin_range(node, body), end_range(node, body)]
|
151
|
+
|
152
|
+
report_offense(node, method_chain, method_call, *removal_ranges) do |corrector|
|
153
|
+
corrector.insert_before(method_call.loc.dot, '&') unless method_call.safe_navigation?
|
154
|
+
end
|
131
155
|
end
|
132
156
|
|
133
|
-
def on_and(node)
|
134
|
-
|
157
|
+
def on_and(node) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength
|
158
|
+
collect_and_clauses(node).each do |(lhs, lhs_operator_range), (rhs, _rhs_operator_range)|
|
159
|
+
lhs_not_nil_check = not_nil_check?(lhs)
|
160
|
+
lhs_receiver = lhs_not_nil_check || lhs
|
161
|
+
rhs_receiver = find_matching_receiver_invocation(strip_begin(rhs), lhs_receiver)
|
162
|
+
|
163
|
+
next if !cop_config['ConvertCodeThatCanStartToReturnNil'] && lhs_not_nil_check
|
164
|
+
next unless offending_node?(node, lhs_receiver, rhs, rhs_receiver)
|
165
|
+
|
166
|
+
# Since we are evaluating every clause in potentially a complex chain of `and` nodes,
|
167
|
+
# we need to ensure that there isn't an object check happening
|
168
|
+
lhs_method_chain = find_method_chain(lhs_receiver)
|
169
|
+
next unless lhs_method_chain == lhs_receiver || lhs_not_nil_check
|
170
|
+
|
171
|
+
report_offense(
|
172
|
+
node,
|
173
|
+
rhs, rhs_receiver,
|
174
|
+
range_with_surrounding_space(range: lhs.source_range, side: :right),
|
175
|
+
range_with_surrounding_space(range: lhs_operator_range, side: :right),
|
176
|
+
offense_range: range_between(lhs.source_range.begin_pos, rhs.source_range.end_pos)
|
177
|
+
)
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
def report_offense(node, rhs, rhs_receiver, *removal_ranges, offense_range: node)
|
182
|
+
add_offense(offense_range) do |corrector|
|
183
|
+
# If the RHS is an `or` we cannot safely autocorrect because in order to remove
|
184
|
+
# the non-nil check we need to add safe-navs to all clauses where the receiver is used
|
185
|
+
next if and_with_rhs_or?(node)
|
186
|
+
|
187
|
+
removal_ranges.each { |range| corrector.remove(range) }
|
188
|
+
yield corrector if block_given?
|
189
|
+
|
190
|
+
handle_comments(corrector, node, rhs)
|
191
|
+
|
192
|
+
add_safe_nav_to_all_methods_in_chain(corrector, rhs_receiver, rhs)
|
193
|
+
end
|
135
194
|
end
|
136
195
|
|
137
196
|
private
|
138
197
|
|
139
|
-
def
|
140
|
-
|
141
|
-
return if receiver != checked_variable || receiver.nil?
|
142
|
-
return if use_var_only_in_unless_modifier?(node, checked_variable)
|
143
|
-
return if chain_length(method_chain, method) > max_chain_length
|
144
|
-
return if unsafe_method_used?(method_chain, method)
|
145
|
-
return if method_chain.method?(:empty?)
|
198
|
+
def find_method_chain(node)
|
199
|
+
return node unless node&.parent&.call_type?
|
146
200
|
|
147
|
-
|
201
|
+
find_method_chain(node.parent)
|
148
202
|
end
|
149
203
|
|
150
|
-
def
|
151
|
-
|
204
|
+
def collect_and_clauses(node)
|
205
|
+
# Collect the lhs, operator and rhs of all `and` nodes
|
206
|
+
# `and` nodes can be nested and can contain `begin` nodes
|
207
|
+
# This gives us a source-ordered list of clauses that is then used to look
|
208
|
+
# for matching receivers as well as operator locations for offense and corrections
|
209
|
+
node.each_descendant(:and)
|
210
|
+
.inject(and_parts(node)) { |nodes, and_node| concat_nodes(nodes, and_node) }
|
211
|
+
.sort_by { |a| a.is_a?(RuboCop::AST::Node) ? a.source_range.begin_pos : a.begin_pos }
|
212
|
+
.each_slice(2)
|
213
|
+
.each_cons(2)
|
152
214
|
end
|
153
215
|
|
154
|
-
def
|
155
|
-
|
156
|
-
method_call = method_call(node)
|
216
|
+
def concat_nodes(nodes, and_node)
|
217
|
+
return nodes if and_node.each_ancestor(:block).any?
|
157
218
|
|
158
|
-
|
159
|
-
|
160
|
-
corrector.insert_before(method_call.loc.dot, '&') unless method_call.safe_navigation?
|
161
|
-
handle_comments(corrector, node, method_call)
|
219
|
+
nodes.concat(and_parts(and_node))
|
220
|
+
end
|
162
221
|
|
163
|
-
|
222
|
+
def and_parts(node)
|
223
|
+
parts = [node.loc.operator]
|
224
|
+
parts << node.rhs unless and_inside_begin?(node.rhs)
|
225
|
+
parts << node.lhs unless node.lhs.and_type? || and_inside_begin?(node.lhs)
|
226
|
+
parts
|
164
227
|
end
|
165
228
|
|
166
|
-
def
|
167
|
-
if
|
229
|
+
def offending_node?(node, lhs_receiver, rhs, rhs_receiver) # rubocop:disable Metrics/CyclomaticComplexity
|
230
|
+
return false if lhs_receiver != rhs_receiver || rhs_receiver.nil?
|
231
|
+
return false if use_var_only_in_unless_modifier?(node, lhs_receiver)
|
232
|
+
return false if chain_length(rhs, rhs_receiver) > max_chain_length
|
233
|
+
return false if unsafe_method_used?(rhs, rhs_receiver.parent)
|
234
|
+
return false if rhs.send_type? && rhs.method?(:empty?)
|
235
|
+
|
236
|
+
true
|
237
|
+
end
|
238
|
+
|
239
|
+
def use_var_only_in_unless_modifier?(node, variable)
|
240
|
+
node.if_type? && node.unless? && !method_called?(variable)
|
241
|
+
end
|
242
|
+
|
243
|
+
def extract_if_body(node)
|
244
|
+
if node.ternary?
|
168
245
|
node.branches.find { |branch| !branch.nil_type? }
|
169
246
|
else
|
170
247
|
node.node_parts[1]
|
@@ -201,20 +278,6 @@ module RuboCop
|
|
201
278
|
node.else? || node.elsif?
|
202
279
|
end
|
203
280
|
|
204
|
-
def method_call(node)
|
205
|
-
_checked_variable, matching_receiver, = extract_parts(node)
|
206
|
-
matching_receiver.parent
|
207
|
-
end
|
208
|
-
|
209
|
-
def extract_parts(node)
|
210
|
-
case node.type
|
211
|
-
when :if
|
212
|
-
extract_parts_from_if(node)
|
213
|
-
when :and
|
214
|
-
extract_parts_from_and(node)
|
215
|
-
end
|
216
|
-
end
|
217
|
-
|
218
281
|
def extract_parts_from_if(node)
|
219
282
|
variable, receiver =
|
220
283
|
if node.ternary?
|
@@ -230,16 +293,6 @@ module RuboCop
|
|
230
293
|
[checked_variable, matching_receiver, receiver, method]
|
231
294
|
end
|
232
295
|
|
233
|
-
def extract_parts_from_and(node)
|
234
|
-
checked_variable, rhs = *node
|
235
|
-
if cop_config['ConvertCodeThatCanStartToReturnNil']
|
236
|
-
checked_variable = not_nil_check?(checked_variable) || checked_variable
|
237
|
-
end
|
238
|
-
|
239
|
-
checked_variable, matching_receiver, method = extract_common_parts(rhs, checked_variable)
|
240
|
-
[checked_variable, matching_receiver, rhs, method]
|
241
|
-
end
|
242
|
-
|
243
296
|
def extract_common_parts(method_chain, checked_variable)
|
244
297
|
matching_receiver = find_matching_receiver_invocation(method_chain, checked_variable)
|
245
298
|
|
@@ -249,7 +302,7 @@ module RuboCop
|
|
249
302
|
end
|
250
303
|
|
251
304
|
def find_matching_receiver_invocation(method_chain, checked_variable)
|
252
|
-
return nil unless method_chain
|
305
|
+
return nil unless method_chain.respond_to?(:receiver)
|
253
306
|
|
254
307
|
receiver = method_chain.receiver
|
255
308
|
|
@@ -259,7 +312,7 @@ module RuboCop
|
|
259
312
|
end
|
260
313
|
|
261
314
|
def chain_length(method_chain, method)
|
262
|
-
method.each_ancestor(:send).inject(
|
315
|
+
method.each_ancestor(:send).inject(0) do |total, ancestor|
|
263
316
|
break total + 1 if ancestor == method_chain
|
264
317
|
|
265
318
|
total + 1
|
@@ -310,6 +363,7 @@ module RuboCop
|
|
310
363
|
start_method.each_ancestor do |ancestor|
|
311
364
|
break unless %i[send block].include?(ancestor.type)
|
312
365
|
next unless ancestor.send_type?
|
366
|
+
next if ancestor.safe_navigation?
|
313
367
|
|
314
368
|
corrector.insert_before(ancestor.loc.dot, '&')
|
315
369
|
|
@@ -0,0 +1,52 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module Style
|
6
|
+
# Enforces safe navigation chains length to not exceed the configured maximum.
|
7
|
+
# The longer the chain is, the harder it becomes to track what on it could be
|
8
|
+
# returning `nil`.
|
9
|
+
#
|
10
|
+
# There is a potential interplay with `Style/SafeNavigation` - if both are enabled
|
11
|
+
# and their settings are "incompatible", one of the cops will complain about what
|
12
|
+
# the other proposes.
|
13
|
+
#
|
14
|
+
# E.g. if `Style/SafeNavigation` is configured with `MaxChainLength: 2` (default)
|
15
|
+
# and this cop is configured with `Max: 1`, then for `foo.bar.baz if foo` the former
|
16
|
+
# will suggest `foo&.bar&.baz`, which is an offense for the latter.
|
17
|
+
#
|
18
|
+
# @example Max: 2 (default)
|
19
|
+
# # bad
|
20
|
+
# user&.address&.zip&.upcase
|
21
|
+
#
|
22
|
+
# # good
|
23
|
+
# user&.address&.zip
|
24
|
+
# user.address.zip if user
|
25
|
+
#
|
26
|
+
class SafeNavigationChainLength < Base
|
27
|
+
MSG = 'Avoid safe navigation chains longer than %<max>d calls.'
|
28
|
+
|
29
|
+
def on_csend(node)
|
30
|
+
safe_navigation_chains = safe_navigation_chains(node)
|
31
|
+
return if safe_navigation_chains.size < max
|
32
|
+
|
33
|
+
add_offense(safe_navigation_chains.last, message: format(MSG, max: max))
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def safe_navigation_chains(node)
|
39
|
+
node.each_ancestor.with_object([]) do |parent, chains|
|
40
|
+
break chains unless parent.csend_type?
|
41
|
+
|
42
|
+
chains << parent
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def max
|
47
|
+
cop_config['Max'] || 2
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -27,7 +27,7 @@ module RuboCop
|
|
27
27
|
# so the correction may not be actually equivalent.
|
28
28
|
#
|
29
29
|
# @example
|
30
|
-
# # bad (select or find_all)
|
30
|
+
# # bad (select, filter, or find_all)
|
31
31
|
# array.select { |x| x.match? /regexp/ }
|
32
32
|
# array.select { |x| /regexp/.match?(x) }
|
33
33
|
# array.select { |x| x =~ /regexp/ }
|
@@ -47,9 +47,11 @@ module RuboCop
|
|
47
47
|
include RangeHelp
|
48
48
|
|
49
49
|
MSG = 'Prefer `%<replacement>s` to `%<original_method>s` with a regexp match.'
|
50
|
-
RESTRICT_ON_SEND = %i[select find_all reject].freeze
|
51
|
-
REPLACEMENTS = { select: 'grep', find_all: 'grep', reject: 'grep_v' }.freeze
|
52
|
-
OPPOSITE_REPLACEMENTS = {
|
50
|
+
RESTRICT_ON_SEND = %i[select filter find_all reject].freeze
|
51
|
+
REPLACEMENTS = { select: 'grep', filter: 'grep', find_all: 'grep', reject: 'grep_v' }.freeze
|
52
|
+
OPPOSITE_REPLACEMENTS = {
|
53
|
+
select: 'grep_v', filter: 'grep_v', find_all: 'grep_v', reject: 'grep'
|
54
|
+
}.freeze
|
53
55
|
REGEXP_METHODS = %i[match? =~ !~].to_set.freeze
|
54
56
|
|
55
57
|
# @!method regexp_match?(node)
|
@@ -84,8 +86,9 @@ module RuboCop
|
|
84
86
|
}
|
85
87
|
PATTERN
|
86
88
|
|
87
|
-
# rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
89
|
+
# rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
88
90
|
def on_send(node)
|
91
|
+
return if target_ruby_version < 2.6 && node.method?(:filter)
|
89
92
|
return unless (block_node = node.block_node)
|
90
93
|
return if block_node.body&.begin_type?
|
91
94
|
return if receiver_allowed?(block_node.receiver)
|
@@ -99,7 +102,7 @@ module RuboCop
|
|
99
102
|
|
100
103
|
register_offense(node, block_node, regexp, replacement)
|
101
104
|
end
|
102
|
-
# rubocop:enable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
105
|
+
# rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
103
106
|
alias on_csend on_send
|
104
107
|
|
105
108
|
private
|
@@ -141,7 +141,7 @@ module RuboCop
|
|
141
141
|
|
142
142
|
def expressions_per_line(exprs)
|
143
143
|
# create a map matching lines to the number of expressions on them
|
144
|
-
exprs_lines = exprs.map(&:
|
144
|
+
exprs_lines = exprs.map(&:last_line)
|
145
145
|
exprs_lines.group_by(&:itself)
|
146
146
|
end
|
147
147
|
|
@@ -33,7 +33,7 @@ module RuboCop
|
|
33
33
|
def on_class(node)
|
34
34
|
return unless struct_constructor?(node.parent_class)
|
35
35
|
|
36
|
-
add_offense(node.parent_class
|
36
|
+
add_offense(node.parent_class) do |corrector|
|
37
37
|
corrector.remove(range_with_surrounding_space(node.loc.keyword, newlines: false))
|
38
38
|
corrector.replace(node.loc.operator, '=')
|
39
39
|
|
@@ -75,7 +75,7 @@ module RuboCop
|
|
75
75
|
|
76
76
|
message = message(node)
|
77
77
|
|
78
|
-
add_offense(node
|
78
|
+
add_offense(node, message: message) do |corrector|
|
79
79
|
autocorrect(corrector, node)
|
80
80
|
end
|
81
81
|
end
|
@@ -171,9 +171,7 @@ module RuboCop
|
|
171
171
|
end
|
172
172
|
|
173
173
|
def unsafe_autocorrect?(condition)
|
174
|
-
condition.children.any?
|
175
|
-
unparenthesized_method_call?(child) || below_ternary_precedence?(child)
|
176
|
-
end
|
174
|
+
condition.children.any? { |child| below_ternary_precedence?(child) }
|
177
175
|
end
|
178
176
|
|
179
177
|
def unparenthesized_method_call?(child)
|
@@ -192,7 +190,7 @@ module RuboCop
|
|
192
190
|
# @!method method_name(node)
|
193
191
|
def_node_matcher :method_name, <<~PATTERN
|
194
192
|
{($:defined? _ ...)
|
195
|
-
(
|
193
|
+
(call {_ nil?} $_ _ ...)}
|
196
194
|
PATTERN
|
197
195
|
|
198
196
|
def correct_parenthesized(corrector, condition)
|
@@ -203,16 +201,39 @@ module RuboCop
|
|
203
201
|
# If we remove the parentheses, we need to add a space or we'll
|
204
202
|
# generate invalid code.
|
205
203
|
corrector.insert_after(condition.loc.end, ' ') unless whitespace_after?(condition)
|
204
|
+
|
205
|
+
if (send_node = condition.child_nodes.last) && node_args_need_parens?(send_node)
|
206
|
+
parenthesize_condition_arguments(corrector, send_node)
|
207
|
+
end
|
206
208
|
end
|
207
209
|
|
208
210
|
def correct_unparenthesized(corrector, condition)
|
209
211
|
corrector.wrap(condition, '(', ')')
|
210
212
|
end
|
211
213
|
|
214
|
+
def parenthesize_condition_arguments(corrector, send_node)
|
215
|
+
range_start = send_node.defined_type? ? send_node.loc.keyword : send_node.loc.selector
|
216
|
+
opening_range = range_start.end.join(send_node.first_argument.source_range.begin)
|
217
|
+
|
218
|
+
corrector.replace(opening_range, '(')
|
219
|
+
corrector.insert_after(send_node.last_argument, ')')
|
220
|
+
end
|
221
|
+
|
212
222
|
def whitespace_after?(node)
|
213
223
|
last_token = processed_source.last_token_of(node)
|
214
224
|
last_token.space_after?
|
215
225
|
end
|
226
|
+
|
227
|
+
def node_args_need_parens?(send_node)
|
228
|
+
return false unless node_with_args?(send_node)
|
229
|
+
return false if send_node.arguments.none? || send_node.parenthesized?
|
230
|
+
|
231
|
+
send_node.dot? || send_node.safe_navigation? || unparenthesized_method_call?(send_node)
|
232
|
+
end
|
233
|
+
|
234
|
+
def node_with_args?(node)
|
235
|
+
node.call_type? || node.defined_type?
|
236
|
+
end
|
216
237
|
end
|
217
238
|
end
|
218
239
|
end
|