rubocop 1.23.0 → 1.25.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/LICENSE.txt +1 -1
- data/README.md +2 -3
- data/config/default.yml +60 -12
- data/lib/rubocop/cli/command/auto_genenerate_config.rb +1 -1
- data/lib/rubocop/cli/command/init_dotfile.rb +1 -1
- data/lib/rubocop/cli/command/show_docs_url.rb +48 -0
- data/lib/rubocop/cli/command/suggest_extensions.rb +1 -1
- data/lib/rubocop/cli.rb +1 -0
- data/lib/rubocop/config_loader_resolver.rb +1 -1
- data/lib/rubocop/cop/bundler/duplicated_gem.rb +1 -1
- data/lib/rubocop/cop/correctors/each_to_for_corrector.rb +1 -1
- data/lib/rubocop/cop/correctors/if_then_corrector.rb +55 -0
- data/lib/rubocop/cop/documentation.rb +19 -2
- data/lib/rubocop/cop/gemspec/require_mfa.rb +8 -10
- data/lib/rubocop/cop/gemspec/required_ruby_version.rb +10 -3
- data/lib/rubocop/cop/generator.rb +4 -3
- data/lib/rubocop/cop/internal_affairs/redundant_method_dispatch_node.rb +47 -0
- data/lib/rubocop/cop/internal_affairs/undefined_config.rb +3 -1
- data/lib/rubocop/cop/internal_affairs.rb +1 -0
- data/lib/rubocop/cop/layout/argument_alignment.rb +36 -9
- data/lib/rubocop/cop/layout/comment_indentation.rb +31 -2
- data/lib/rubocop/cop/layout/dot_position.rb +4 -0
- data/lib/rubocop/cop/layout/empty_lines_around_exception_handling_keywords.rb +5 -1
- data/lib/rubocop/cop/layout/hash_alignment.rb +7 -2
- data/lib/rubocop/cop/layout/rescue_ensure_alignment.rb +1 -1
- data/lib/rubocop/cop/layout/space_after_colon.rb +1 -1
- data/lib/rubocop/cop/layout/space_before_first_arg.rb +4 -0
- data/lib/rubocop/cop/lint/ambiguous_regexp_literal.rb +5 -1
- data/lib/rubocop/cop/lint/constant_definition_in_block.rb +1 -1
- data/lib/rubocop/cop/lint/deprecated_class_methods.rb +16 -4
- data/lib/rubocop/cop/lint/deprecated_open_ssl_constant.rb +6 -0
- data/lib/rubocop/cop/lint/each_with_object_argument.rb +1 -1
- data/lib/rubocop/cop/lint/incompatible_io_select_with_fiber_scheduler.rb +10 -5
- data/lib/rubocop/cop/lint/parentheses_as_grouped_expression.rb +7 -4
- data/lib/rubocop/cop/metrics/block_length.rb +1 -0
- data/lib/rubocop/cop/metrics/cyclomatic_complexity.rb +0 -9
- data/lib/rubocop/cop/metrics/method_length.rb +1 -0
- data/lib/rubocop/cop/metrics/module_length.rb +1 -1
- data/lib/rubocop/cop/metrics/utils/code_length_calculator.rb +1 -1
- data/lib/rubocop/cop/metrics/utils/repeated_attribute_discount.rb +1 -1
- data/lib/rubocop/cop/mixin/enforce_superclass.rb +5 -0
- data/lib/rubocop/cop/mixin/hash_alignment_styles.rb +4 -3
- data/lib/rubocop/cop/mixin/hash_shorthand_syntax.rb +82 -0
- data/lib/rubocop/cop/naming/block_forwarding.rb +121 -0
- data/lib/rubocop/cop/naming/method_parameter_name.rb +1 -1
- data/lib/rubocop/cop/security/open.rb +11 -1
- data/lib/rubocop/cop/style/character_literal.rb +8 -1
- data/lib/rubocop/cop/style/collection_compact.rb +31 -13
- data/lib/rubocop/cop/style/combinable_loops.rb +2 -2
- data/lib/rubocop/cop/style/empty_case_condition.rb +10 -0
- data/lib/rubocop/cop/style/file_read.rb +112 -0
- data/lib/rubocop/cop/style/file_write.rb +124 -0
- data/lib/rubocop/cop/style/hash_conversion.rb +2 -1
- data/lib/rubocop/cop/style/hash_syntax.rb +36 -0
- data/lib/rubocop/cop/style/hash_transform_keys.rb +6 -6
- data/lib/rubocop/cop/style/hash_transform_values.rb +6 -6
- data/lib/rubocop/cop/style/if_inside_else.rb +15 -0
- data/lib/rubocop/cop/style/if_with_boolean_literal_branches.rb +1 -1
- data/lib/rubocop/cop/style/map_to_hash.rb +68 -0
- data/lib/rubocop/cop/style/method_call_with_args_parentheses/omit_parentheses.rb +20 -0
- data/lib/rubocop/cop/style/method_call_with_args_parentheses.rb +3 -1
- data/lib/rubocop/cop/style/method_def_parentheses.rb +17 -13
- data/lib/rubocop/cop/style/numeric_literals.rb +10 -1
- data/lib/rubocop/cop/style/one_line_conditional.rb +18 -39
- data/lib/rubocop/cop/style/redundant_begin.rb +2 -6
- data/lib/rubocop/cop/style/redundant_interpolation.rb +17 -3
- data/lib/rubocop/cop/style/redundant_regexp_character_class.rb +5 -1
- data/lib/rubocop/cop/style/redundant_self.rb +1 -1
- data/lib/rubocop/cop/style/safe_navigation.rb +1 -5
- data/lib/rubocop/cop/style/sample.rb +5 -3
- data/lib/rubocop/cop/style/single_line_block_params.rb +2 -2
- data/lib/rubocop/cop/style/sole_nested_conditional.rb +3 -1
- data/lib/rubocop/cop/style/swap_values.rb +2 -0
- data/lib/rubocop/cop/style/ternary_parentheses.rb +16 -2
- data/lib/rubocop/cop/team.rb +1 -1
- data/lib/rubocop/cop/util.rb +9 -1
- data/lib/rubocop/formatter/disabled_config_formatter.rb +16 -2
- data/lib/rubocop/options.rb +6 -1
- data/lib/rubocop/remote_config.rb +1 -3
- data/lib/rubocop/result_cache.rb +1 -1
- data/lib/rubocop/version.rb +1 -1
- data/lib/rubocop.rb +7 -0
- metadata +15 -7
@@ -32,6 +32,19 @@ module RuboCop
|
|
32
32
|
# true
|
33
33
|
# end
|
34
34
|
#
|
35
|
+
# @example AllowForAlignment: false (default)
|
36
|
+
# # bad
|
37
|
+
# a = 1 # A really long comment
|
38
|
+
# # spanning two lines.
|
39
|
+
#
|
40
|
+
# # good
|
41
|
+
# # A really long comment spanning one line.
|
42
|
+
# a = 1
|
43
|
+
#
|
44
|
+
# @example AllowForAlignment: true
|
45
|
+
# # good
|
46
|
+
# a = 1 # A really long comment
|
47
|
+
# # spanning two lines.
|
35
48
|
class CommentIndentation < Base
|
36
49
|
include Alignment
|
37
50
|
extend AutoCorrector
|
@@ -40,7 +53,7 @@ module RuboCop
|
|
40
53
|
'instead of %<correct_comment_indentation>d).'
|
41
54
|
|
42
55
|
def on_new_investigation
|
43
|
-
processed_source.comments.
|
56
|
+
processed_source.comments.each_with_index { |comment, ix| check(comment, ix) }
|
44
57
|
end
|
45
58
|
|
46
59
|
private
|
@@ -76,7 +89,7 @@ module RuboCop
|
|
76
89
|
AlignmentCorrector.correct(corrector, processed_source, comment, @column_delta)
|
77
90
|
end
|
78
91
|
|
79
|
-
def check(comment)
|
92
|
+
def check(comment, comment_index)
|
80
93
|
return unless own_line_comment?(comment)
|
81
94
|
|
82
95
|
next_line = line_after_comment(comment)
|
@@ -94,11 +107,27 @@ module RuboCop
|
|
94
107
|
return if column == correct_comment_indentation
|
95
108
|
end
|
96
109
|
|
110
|
+
return if correctly_aligned_with_preceding_comment?(comment_index, column)
|
111
|
+
|
97
112
|
add_offense(comment, message: message(column, correct_comment_indentation)) do |corrector|
|
98
113
|
autocorrect(corrector, comment)
|
99
114
|
end
|
100
115
|
end
|
101
116
|
|
117
|
+
# Returns true if:
|
118
|
+
# a) the cop is configured to allow extra indentation for alignment, and
|
119
|
+
# b) the currently inspected comment is aligned with the nearest preceding end-of-line
|
120
|
+
# comment.
|
121
|
+
def correctly_aligned_with_preceding_comment?(comment_index, column)
|
122
|
+
return false unless cop_config['AllowForAlignment']
|
123
|
+
|
124
|
+
processed_source.comments[0...comment_index].reverse_each do |other_comment|
|
125
|
+
return other_comment.loc.column == column unless own_line_comment?(other_comment)
|
126
|
+
end
|
127
|
+
|
128
|
+
false
|
129
|
+
end
|
130
|
+
|
102
131
|
def message(column, correct_comment_indentation)
|
103
132
|
format(MSG, column: column, correct_comment_indentation: correct_comment_indentation)
|
104
133
|
end
|
@@ -81,7 +81,7 @@ module RuboCop
|
|
81
81
|
|
82
82
|
locations.each do |loc|
|
83
83
|
line = loc.line
|
84
|
-
next if line == line_of_def_or_kwbegin
|
84
|
+
next if line == line_of_def_or_kwbegin || last_rescue_and_end_on_same_line(body)
|
85
85
|
|
86
86
|
keyword = loc.source
|
87
87
|
# below the keyword
|
@@ -91,6 +91,10 @@ module RuboCop
|
|
91
91
|
end
|
92
92
|
end
|
93
93
|
|
94
|
+
def last_rescue_and_end_on_same_line(body)
|
95
|
+
body.rescue_type? && body.resbody_branches.last.loc.line == body.parent.loc.end.line
|
96
|
+
end
|
97
|
+
|
94
98
|
def message(location, keyword)
|
95
99
|
format(MSG, location: location, keyword: keyword)
|
96
100
|
end
|
@@ -222,11 +222,16 @@ module RuboCop
|
|
222
222
|
node.pairs.any? &&
|
223
223
|
node.parent&.call_type?
|
224
224
|
|
225
|
+
left_sibling = argument_before_hash(node)
|
225
226
|
parent_loc = node.parent.loc
|
226
|
-
selector = parent_loc.selector || parent_loc.expression
|
227
|
+
selector = left_sibling || parent_loc.selector || parent_loc.expression
|
227
228
|
same_line?(selector, node.pairs.first)
|
228
229
|
end
|
229
230
|
|
231
|
+
def argument_before_hash(hash_node)
|
232
|
+
hash_node.left_sibling.respond_to?(:loc) ? hash_node.left_sibling : nil
|
233
|
+
end
|
234
|
+
|
230
235
|
def reset!
|
231
236
|
self.offenses_by = {}
|
232
237
|
self.column_deltas = Hash.new { |hash, key| hash[key] = {} }
|
@@ -313,7 +318,7 @@ module RuboCop
|
|
313
318
|
# just give each lambda the same reference and they would all get the
|
314
319
|
# last value of each. A local variable fixes the problem.
|
315
320
|
|
316
|
-
if node.value
|
321
|
+
if node.value && node.respond_to?(:value_omission?) && !node.value_omission?
|
317
322
|
correct_key_value(corrector, delta, node.key.source_range,
|
318
323
|
node.value.source_range,
|
319
324
|
node.loc.operator)
|
@@ -143,7 +143,7 @@ module RuboCop
|
|
143
143
|
return true
|
144
144
|
end
|
145
145
|
|
146
|
-
do_keyword_line == selector
|
146
|
+
do_keyword_line == selector&.line && rescue_keyword_column == selector.column
|
147
147
|
end
|
148
148
|
|
149
149
|
def aligned_with_leading_dot?(do_keyword_line, send_node_loc, rescue_keyword_column)
|
@@ -28,6 +28,10 @@ module RuboCop
|
|
28
28
|
|
29
29
|
MSG = 'Put one space between the method name and the first argument.'
|
30
30
|
|
31
|
+
def self.autocorrect_incompatible_with
|
32
|
+
[Style::MethodCallWithArgsParentheses]
|
33
|
+
end
|
34
|
+
|
31
35
|
def on_send(node)
|
32
36
|
return unless regular_method_call_with_arguments?(node)
|
33
37
|
|
@@ -30,7 +30,11 @@ module RuboCop
|
|
30
30
|
|
31
31
|
def on_new_investigation
|
32
32
|
processed_source.diagnostics.each do |diagnostic|
|
33
|
-
|
33
|
+
if target_ruby_version >= 3.0
|
34
|
+
next unless diagnostic.reason == :ambiguous_regexp
|
35
|
+
else
|
36
|
+
next unless diagnostic.reason == :ambiguous_literal
|
37
|
+
end
|
34
38
|
|
35
39
|
offense_node = find_offense_node_by(diagnostic)
|
36
40
|
|
@@ -12,6 +12,7 @@ module RuboCop
|
|
12
12
|
# File.exists?(some_path)
|
13
13
|
# Dir.exists?(some_path)
|
14
14
|
# iterator?
|
15
|
+
# ENV.freeze # Calling `Env.freeze` raises `TypeError` since Ruby 2.7.
|
15
16
|
# Socket.gethostbyname(host)
|
16
17
|
# Socket.gethostbyaddr(host)
|
17
18
|
#
|
@@ -22,6 +23,7 @@ module RuboCop
|
|
22
23
|
# File.exist?(some_path)
|
23
24
|
# Dir.exist?(some_path)
|
24
25
|
# block_given?
|
26
|
+
# ENV # `ENV.freeze` cannot prohibit changes to environment variables.
|
25
27
|
# Addrinfo.getaddrinfo(nodename, service)
|
26
28
|
# Addrinfo.tcp(host, port).getnameinfo
|
27
29
|
class DeprecatedClassMethods < Base
|
@@ -106,6 +108,9 @@ module RuboCop
|
|
106
108
|
|
107
109
|
DeprecatedClassMethod.new(:iterator?) => Replacement.new(:block_given?),
|
108
110
|
|
111
|
+
DeprecatedClassMethod.new(:freeze, class_constant: :ENV) =>
|
112
|
+
Replacement.new(nil, class_constant: :ENV),
|
113
|
+
|
109
114
|
DeprecatedClassMethod.new(:gethostbyaddr, class_constant: :Socket, correctable: false) =>
|
110
115
|
Replacement.new(:getnameinfo, class_constant: :Addrinfo, instance_method: true),
|
111
116
|
|
@@ -120,11 +125,18 @@ module RuboCop
|
|
120
125
|
|
121
126
|
def on_send(node)
|
122
127
|
check(node) do |deprecated|
|
123
|
-
|
128
|
+
prefer = replacement(deprecated)
|
129
|
+
message = format(MSG, current: deprecated, prefer: prefer)
|
130
|
+
current_method = node.loc.selector
|
131
|
+
|
132
|
+
add_offense(current_method, message: message) do |corrector|
|
133
|
+
next unless deprecated.correctable?
|
124
134
|
|
125
|
-
|
126
|
-
|
127
|
-
|
135
|
+
if (preferred_method = prefer.method)
|
136
|
+
corrector.replace(current_method, preferred_method)
|
137
|
+
else
|
138
|
+
corrector.remove(node.loc.dot)
|
139
|
+
corrector.remove(current_method)
|
128
140
|
end
|
129
141
|
end
|
130
142
|
end
|
@@ -55,8 +55,14 @@ module RuboCop
|
|
55
55
|
...)
|
56
56
|
PATTERN
|
57
57
|
|
58
|
+
# @!method digest_const?(node)
|
59
|
+
def_node_matcher :digest_const?, <<~PATTERN
|
60
|
+
(const _ :Digest)
|
61
|
+
PATTERN
|
62
|
+
|
58
63
|
def on_send(node)
|
59
64
|
return if node.arguments.any? { |arg| arg.variable? || arg.send_type? || arg.const_type? }
|
65
|
+
return if digest_const?(node.receiver)
|
60
66
|
return unless algorithm_const(node)
|
61
67
|
|
62
68
|
message = message(node)
|
@@ -20,6 +20,10 @@ module RuboCop
|
|
20
20
|
# # good
|
21
21
|
# io.wait_writable(timeout)
|
22
22
|
#
|
23
|
+
# @safety
|
24
|
+
# This cop's autocorrection is unsafe because `NoMethodError` occurs
|
25
|
+
# if `require 'io/wait'` is not called.
|
26
|
+
#
|
23
27
|
class IncompatibleIoSelectWithFiberScheduler < Base
|
24
28
|
extend AutoCorrector
|
25
29
|
|
@@ -29,11 +33,12 @@ module RuboCop
|
|
29
33
|
# @!method io_select(node)
|
30
34
|
def_node_matcher :io_select, <<~PATTERN
|
31
35
|
(send
|
32
|
-
(const {nil? cbase} :IO) :select
|
36
|
+
(const {nil? cbase} :IO) :select $...)
|
33
37
|
PATTERN
|
34
38
|
|
35
39
|
def on_send(node)
|
36
|
-
|
40
|
+
read, write, _excepts, timeout = *io_select(node)
|
41
|
+
return unless read
|
37
42
|
return unless scheduler_compatible?(read, write) || scheduler_compatible?(write, read)
|
38
43
|
|
39
44
|
preferred = preferred_method(read, write, timeout)
|
@@ -47,13 +52,13 @@ module RuboCop
|
|
47
52
|
private
|
48
53
|
|
49
54
|
def scheduler_compatible?(io1, io2)
|
50
|
-
return false unless io1
|
55
|
+
return false unless io1&.array_type? && io1.values.size == 1
|
51
56
|
|
52
|
-
io2
|
57
|
+
io2&.array_type? ? io2.values.empty? : (io2.nil? || io2.nil_type?)
|
53
58
|
end
|
54
59
|
|
55
60
|
def preferred_method(read, write, timeout)
|
56
|
-
timeout_argument = timeout.
|
61
|
+
timeout_argument = timeout.nil? ? '' : "(#{timeout.source})"
|
57
62
|
|
58
63
|
if read.array_type? && read.values[0]
|
59
64
|
"#{read.values[0].source}.wait_readable#{timeout_argument}"
|
@@ -41,7 +41,11 @@ module RuboCop
|
|
41
41
|
end
|
42
42
|
|
43
43
|
node.operator_method? || node.setter_method? || chained_calls?(node) ||
|
44
|
-
|
44
|
+
valid_first_argument?(node.first_argument)
|
45
|
+
end
|
46
|
+
|
47
|
+
def valid_first_argument?(first_arg)
|
48
|
+
first_arg.operator_keyword? || first_arg.hash_type? || ternary_expression?(first_arg)
|
45
49
|
end
|
46
50
|
|
47
51
|
def first_argument_starts_with_left_parenthesis?(node)
|
@@ -53,9 +57,8 @@ module RuboCop
|
|
53
57
|
first_argument.send_type? && (node.children.last&.children&.count || 0) > 1
|
54
58
|
end
|
55
59
|
|
56
|
-
def
|
57
|
-
|
58
|
-
first_argument.operator_keyword?
|
60
|
+
def ternary_expression?(node)
|
61
|
+
node.if_type? && node.ternary?
|
59
62
|
end
|
60
63
|
|
61
64
|
def spaces_before_left_parenthesis(node)
|
@@ -46,15 +46,6 @@ module RuboCop
|
|
46
46
|
1
|
47
47
|
end
|
48
48
|
|
49
|
-
def block_method(node)
|
50
|
-
case node.type
|
51
|
-
when :block
|
52
|
-
node.method_name
|
53
|
-
when :block_pass
|
54
|
-
node.parent.method_name
|
55
|
-
end
|
56
|
-
end
|
57
|
-
|
58
49
|
def count_block?(block)
|
59
50
|
KNOWN_ITERATING_METHODS.include? block.method_name
|
60
51
|
end
|
@@ -44,7 +44,7 @@ module RuboCop
|
|
44
44
|
|
45
45
|
# @!method module_definition?(node)
|
46
46
|
def_node_matcher :module_definition?, <<~PATTERN
|
47
|
-
(casgn nil? _ (block (send (const {nil? cbase} :Module) :new) ...))
|
47
|
+
(casgn nil? _ ({block numblock} (send (const {nil? cbase} :Module) :new) ...))
|
48
48
|
PATTERN
|
49
49
|
|
50
50
|
def message(length, max_length)
|
@@ -13,6 +13,11 @@ module RuboCop
|
|
13
13
|
# @api private
|
14
14
|
module EnforceSuperclass
|
15
15
|
def self.included(base)
|
16
|
+
warn Rainbow(
|
17
|
+
'`RuboCop::Cop::EnforceSuperclass` is deprecated and will be removed in RuboCop 2.0. ' \
|
18
|
+
'Please upgrade to RuboCop Rails 2.9 or newer to continue.'
|
19
|
+
).yellow
|
20
|
+
|
16
21
|
# @!method class_definition(node)
|
17
22
|
base.def_node_matcher :class_definition, <<~PATTERN
|
18
23
|
(class (const _ !:#{base::SUPERCLASS}) #{base::BASE_PATTERN} ...)
|
@@ -43,7 +43,7 @@ module RuboCop
|
|
43
43
|
end
|
44
44
|
|
45
45
|
def value_delta(pair)
|
46
|
-
return 0 if pair.value_on_new_line?
|
46
|
+
return 0 if pair.value_on_new_line? || pair.value_omission?
|
47
47
|
|
48
48
|
correct_value_column = pair.loc.operator.end.column + 1
|
49
49
|
actual_value_column = pair.value.loc.column
|
@@ -111,7 +111,8 @@ module RuboCop
|
|
111
111
|
correct_value_column = first_pair.key.loc.column +
|
112
112
|
current_pair.delimiter(true).length +
|
113
113
|
max_key_width
|
114
|
-
|
114
|
+
|
115
|
+
current_pair.value_omission? ? 0 : correct_value_column - current_pair.value.loc.column
|
115
116
|
end
|
116
117
|
end
|
117
118
|
|
@@ -134,7 +135,7 @@ module RuboCop
|
|
134
135
|
end
|
135
136
|
|
136
137
|
def value_delta(first_pair, current_pair)
|
137
|
-
first_pair.value_delta(current_pair)
|
138
|
+
current_pair.value_omission? ? 0 : first_pair.value_delta(current_pair)
|
138
139
|
end
|
139
140
|
end
|
140
141
|
|
@@ -0,0 +1,82 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
# This module checks for Ruby 3.1's hash value omission syntax.
|
6
|
+
module HashShorthandSyntax
|
7
|
+
OMIT_HASH_VALUE_MSG = 'Omit the hash value.'
|
8
|
+
EXPLICIT_HASH_VALUE_MSG = 'Explicit the hash value.'
|
9
|
+
|
10
|
+
def on_pair(node)
|
11
|
+
return if ignore_hash_shorthand_syntax?(node)
|
12
|
+
|
13
|
+
hash_key_source = node.key.source
|
14
|
+
|
15
|
+
if enforced_shorthand_syntax == 'always'
|
16
|
+
return if node.value_omission? || require_hash_value?(hash_key_source, node)
|
17
|
+
|
18
|
+
message = OMIT_HASH_VALUE_MSG
|
19
|
+
replacement = "#{hash_key_source}:"
|
20
|
+
else
|
21
|
+
return unless node.value_omission?
|
22
|
+
|
23
|
+
message = EXPLICIT_HASH_VALUE_MSG
|
24
|
+
replacement = "#{hash_key_source}: #{hash_key_source}"
|
25
|
+
end
|
26
|
+
|
27
|
+
add_offense(node.value, message: message) do |corrector|
|
28
|
+
corrector.replace(node, replacement)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def ignore_hash_shorthand_syntax?(pair_node)
|
35
|
+
target_ruby_version <= 3.0 || enforced_shorthand_syntax == 'either' ||
|
36
|
+
!pair_node.parent.hash_type?
|
37
|
+
end
|
38
|
+
|
39
|
+
def enforced_shorthand_syntax
|
40
|
+
cop_config.fetch('EnforcedShorthandSyntax', 'always')
|
41
|
+
end
|
42
|
+
|
43
|
+
def require_hash_value?(hash_key_source, node)
|
44
|
+
return true if require_hash_value_for_around_hash_literal?(node)
|
45
|
+
|
46
|
+
hash_value = node.value
|
47
|
+
return true unless hash_value.send_type? || hash_value.lvar_type?
|
48
|
+
|
49
|
+
hash_key_source != hash_value.source || hash_key_source.end_with?('!', '?')
|
50
|
+
end
|
51
|
+
|
52
|
+
def require_hash_value_for_around_hash_literal?(node)
|
53
|
+
return false unless (ancestor = node.parent.parent)
|
54
|
+
return false if ancestor.send_type? && ancestor.method?(:[])
|
55
|
+
|
56
|
+
!node.parent.braces? && !use_element_of_hash_literal_as_receiver?(ancestor, node.parent) &&
|
57
|
+
(use_modifier_form_without_parenthesized_method_call?(ancestor) ||
|
58
|
+
without_parentheses_call_expr_follows?(ancestor))
|
59
|
+
end
|
60
|
+
|
61
|
+
def use_element_of_hash_literal_as_receiver?(ancestor, parent)
|
62
|
+
# `{value:}.do_something` is a valid syntax.
|
63
|
+
ancestor.send_type? && ancestor.receiver == parent
|
64
|
+
end
|
65
|
+
|
66
|
+
def use_modifier_form_without_parenthesized_method_call?(ancestor)
|
67
|
+
return false if ancestor.respond_to?(:parenthesized?) && ancestor.parenthesized?
|
68
|
+
return false unless (parent = ancestor.parent)
|
69
|
+
|
70
|
+
parent.respond_to?(:modifier_form?) && parent.modifier_form?
|
71
|
+
end
|
72
|
+
|
73
|
+
def without_parentheses_call_expr_follows?(ancestor)
|
74
|
+
right_sibling = ancestor.right_sibling
|
75
|
+
right_sibling ||= ancestor.each_ancestor.find(&:assignment?)&.right_sibling
|
76
|
+
return false unless right_sibling
|
77
|
+
|
78
|
+
ancestor.respond_to?(:parenthesized?) && !ancestor.parenthesized? && !!right_sibling
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,121 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module Naming
|
6
|
+
# In Ruby 3.1, anonymous block forwarding has been added.
|
7
|
+
#
|
8
|
+
# This cop identifies places where `do_something(&block)` can be replaced
|
9
|
+
# by `do_something(&)`.
|
10
|
+
#
|
11
|
+
# It also supports the opposite style by alternative `explicit` option.
|
12
|
+
# You can specify the block variable name for auto-correction with `BlockForwardingName`.
|
13
|
+
# The default variable name is `block`. If the name is already in use, it will not be
|
14
|
+
# auto-corrected.
|
15
|
+
#
|
16
|
+
# @example EnforcedStyle: anonymous (default)
|
17
|
+
#
|
18
|
+
# # bad
|
19
|
+
# def foo(&block)
|
20
|
+
# bar(&block)
|
21
|
+
# end
|
22
|
+
#
|
23
|
+
# # good
|
24
|
+
# def foo(&)
|
25
|
+
# bar(&)
|
26
|
+
# end
|
27
|
+
#
|
28
|
+
# @example EnforcedStyle: explicit
|
29
|
+
#
|
30
|
+
# # bad
|
31
|
+
# def foo(&)
|
32
|
+
# bar(&)
|
33
|
+
# end
|
34
|
+
#
|
35
|
+
# # good
|
36
|
+
# def foo(&block)
|
37
|
+
# bar(&block)
|
38
|
+
# end
|
39
|
+
#
|
40
|
+
class BlockForwarding < Base
|
41
|
+
include ConfigurableEnforcedStyle
|
42
|
+
include RangeHelp
|
43
|
+
extend AutoCorrector
|
44
|
+
extend TargetRubyVersion
|
45
|
+
|
46
|
+
minimum_target_ruby_version 3.1
|
47
|
+
|
48
|
+
MSG = 'Use %<style>s block forwarding.'
|
49
|
+
|
50
|
+
def on_def(node)
|
51
|
+
return if node.arguments.empty?
|
52
|
+
|
53
|
+
last_argument = node.arguments.last
|
54
|
+
return if expected_block_forwarding_style?(node, last_argument)
|
55
|
+
|
56
|
+
register_offense(last_argument, node)
|
57
|
+
|
58
|
+
node.each_descendant(:block_pass) do |block_pass_node|
|
59
|
+
next if block_pass_node.children.first&.sym_type? ||
|
60
|
+
last_argument.source != block_pass_node.source
|
61
|
+
|
62
|
+
register_offense(block_pass_node, node)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
alias on_defs on_def
|
66
|
+
|
67
|
+
private
|
68
|
+
|
69
|
+
def expected_block_forwarding_style?(node, last_argument)
|
70
|
+
if style == :anonymous
|
71
|
+
!explicit_block_argument?(last_argument) ||
|
72
|
+
use_kwarg_in_method_definition?(node) ||
|
73
|
+
use_block_argument_as_local_variable?(node, last_argument.source[1..-1])
|
74
|
+
else
|
75
|
+
!anonymous_block_argument?(last_argument)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def use_kwarg_in_method_definition?(node)
|
80
|
+
node.arguments.each_descendant(:kwarg, :kwoptarg).any?
|
81
|
+
end
|
82
|
+
|
83
|
+
def anonymous_block_argument?(node)
|
84
|
+
node.blockarg_type? && node.name.nil?
|
85
|
+
end
|
86
|
+
|
87
|
+
def explicit_block_argument?(node)
|
88
|
+
node.blockarg_type? && !node.name.nil?
|
89
|
+
end
|
90
|
+
|
91
|
+
def register_offense(block_argument, node)
|
92
|
+
add_offense(block_argument, message: format(MSG, style: style)) do |corrector|
|
93
|
+
if style == :anonymous
|
94
|
+
corrector.replace(block_argument, '&')
|
95
|
+
|
96
|
+
arguments = block_argument.parent
|
97
|
+
|
98
|
+
add_parentheses(arguments, corrector) unless arguments.parenthesized_call?
|
99
|
+
else
|
100
|
+
unless use_block_argument_as_local_variable?(node, block_forwarding_name)
|
101
|
+
corrector.replace(block_argument, "&#{block_forwarding_name}")
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
def use_block_argument_as_local_variable?(node, last_argument)
|
108
|
+
return if node.body.nil?
|
109
|
+
|
110
|
+
node.body.each_descendant(:lvar).any? do |lvar|
|
111
|
+
!lvar.parent.block_pass_type? && lvar.source == last_argument
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
def block_forwarding_name
|
116
|
+
cop_config.fetch('BlockForwardingName', 'block')
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|