rubocop 1.23.0 → 1.25.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/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
|