rubocop 1.60.2 → 1.63.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +1 -1
- data/assets/output.css.erb +159 -0
- data/assets/output.html.erb +1 -160
- data/config/default.yml +64 -15
- data/lib/rubocop/cli/command/auto_generate_config.rb +12 -3
- data/lib/rubocop/cli/command/lsp.rb +2 -2
- data/lib/rubocop/cli.rb +6 -1
- data/lib/rubocop/config.rb +37 -10
- data/lib/rubocop/config_finder.rb +12 -2
- data/lib/rubocop/config_obsoletion.rb +1 -1
- data/lib/rubocop/config_validator.rb +14 -5
- data/lib/rubocop/cop/autocorrect_logic.rb +6 -1
- data/lib/rubocop/cop/base.rb +52 -6
- data/lib/rubocop/cop/correctors/each_to_for_corrector.rb +4 -8
- data/lib/rubocop/cop/correctors/for_to_each_corrector.rb +5 -13
- data/lib/rubocop/cop/gemspec/required_ruby_version.rb +5 -1
- data/lib/rubocop/cop/internal_affairs/example_description.rb +1 -0
- data/lib/rubocop/cop/internal_affairs/method_name_end_with.rb +8 -6
- data/lib/rubocop/cop/internal_affairs/node_matcher_directive.rb +122 -28
- data/lib/rubocop/cop/internal_affairs/redundant_expect_offense_arguments.rb +34 -0
- data/lib/rubocop/cop/internal_affairs.rb +1 -0
- data/lib/rubocop/cop/layout/empty_line_after_magic_comment.rb +14 -7
- data/lib/rubocop/cop/layout/end_alignment.rb +3 -1
- data/lib/rubocop/cop/layout/redundant_line_break.rb +11 -3
- data/lib/rubocop/cop/layout/space_before_block_braces.rb +19 -10
- data/lib/rubocop/cop/layout/space_inside_hash_literal_braces.rb +1 -1
- data/lib/rubocop/cop/lint/assignment_in_condition.rb +2 -4
- data/lib/rubocop/cop/lint/debugger.rb +27 -2
- data/lib/rubocop/cop/lint/empty_conditional_body.rb +1 -1
- data/lib/rubocop/cop/lint/redundant_safe_navigation.rb +14 -9
- data/lib/rubocop/cop/lint/redundant_with_index.rb +4 -0
- data/lib/rubocop/cop/lint/rescue_type.rb +1 -3
- data/lib/rubocop/cop/lint/script_permission.rb +3 -3
- data/lib/rubocop/cop/lint/syntax.rb +1 -1
- data/lib/rubocop/cop/lint/to_enum_arguments.rb +7 -2
- data/lib/rubocop/cop/lint/useless_times.rb +1 -1
- data/lib/rubocop/cop/lint/void.rb +6 -1
- data/lib/rubocop/cop/mixin/code_length.rb +12 -1
- data/lib/rubocop/cop/mixin/method_complexity.rb +15 -6
- data/lib/rubocop/cop/mixin/multiline_expression_indentation.rb +1 -1
- data/lib/rubocop/cop/mixin/safe_assignment.rb +1 -1
- data/lib/rubocop/cop/naming/block_forwarding.rb +31 -12
- data/lib/rubocop/cop/naming/file_name.rb +2 -2
- data/lib/rubocop/cop/naming/inclusive_language.rb +1 -2
- data/lib/rubocop/cop/naming/predicate_name.rb +2 -2
- data/lib/rubocop/cop/registry.rb +1 -1
- data/lib/rubocop/cop/style/alias.rb +1 -0
- data/lib/rubocop/cop/style/arguments_forwarding.rb +29 -8
- data/lib/rubocop/cop/style/case_like_if.rb +1 -1
- data/lib/rubocop/cop/style/class_vars.rb +3 -3
- data/lib/rubocop/cop/style/collection_compact.rb +3 -3
- data/lib/rubocop/cop/style/commented_keyword.rb +5 -2
- data/lib/rubocop/cop/style/conditional_assignment.rb +4 -5
- data/lib/rubocop/cop/style/copyright.rb +16 -11
- data/lib/rubocop/cop/style/eval_with_location.rb +2 -0
- data/lib/rubocop/cop/style/exact_regexp_match.rb +2 -1
- data/lib/rubocop/cop/style/for.rb +2 -0
- data/lib/rubocop/cop/style/format_string.rb +9 -9
- data/lib/rubocop/cop/style/hash_each_methods.rb +1 -1
- data/lib/rubocop/cop/style/hash_syntax.rb +6 -2
- data/lib/rubocop/cop/style/inverse_methods.rb +8 -8
- data/lib/rubocop/cop/style/invertible_unless_condition.rb +10 -5
- data/lib/rubocop/cop/style/map_compact_with_conditional_block.rb +5 -8
- data/lib/rubocop/cop/style/map_into_array.rb +175 -0
- data/lib/rubocop/cop/style/map_to_hash.rb +1 -1
- data/lib/rubocop/cop/style/map_to_set.rb +1 -1
- data/lib/rubocop/cop/style/method_call_with_args_parentheses/omit_parentheses.rb +1 -1
- data/lib/rubocop/cop/style/multiline_method_signature.rb +10 -1
- data/lib/rubocop/cop/style/multiline_ternary_operator.rb +4 -0
- data/lib/rubocop/cop/style/nil_comparison.rb +2 -0
- data/lib/rubocop/cop/style/object_then.rb +5 -3
- data/lib/rubocop/cop/style/parallel_assignment.rb +1 -3
- data/lib/rubocop/cop/style/raise_args.rb +4 -1
- data/lib/rubocop/cop/style/redundant_argument.rb +25 -2
- data/lib/rubocop/cop/style/redundant_assignment.rb +10 -2
- data/lib/rubocop/cop/style/redundant_current_directory_in_path.rb +5 -4
- data/lib/rubocop/cop/style/redundant_each.rb +1 -1
- data/lib/rubocop/cop/style/redundant_filter_chain.rb +1 -1
- data/lib/rubocop/cop/style/redundant_line_continuation.rb +5 -0
- data/lib/rubocop/cop/style/redundant_percent_q.rb +1 -1
- data/lib/rubocop/cop/style/redundant_return.rb +6 -0
- data/lib/rubocop/cop/style/sample.rb +1 -3
- data/lib/rubocop/cop/team.rb +3 -0
- data/lib/rubocop/cop/utils/regexp_ranges.rb +1 -1
- data/lib/rubocop/cops_documentation_generator.rb +4 -2
- data/lib/rubocop/directive_comment.rb +10 -8
- data/lib/rubocop/formatter/clang_style_formatter.rb +3 -7
- data/lib/rubocop/formatter/html_formatter.rb +30 -10
- data/lib/rubocop/formatter/offense_count_formatter.rb +12 -2
- data/lib/rubocop/formatter/tap_formatter.rb +3 -7
- data/lib/rubocop/lockfile.rb +34 -4
- data/lib/rubocop/lsp/logger.rb +1 -1
- data/lib/rubocop/lsp/routes.rb +1 -1
- data/lib/rubocop/lsp/runtime.rb +1 -1
- data/lib/rubocop/lsp/server.rb +5 -2
- data/lib/rubocop/lsp/severity.rb +1 -1
- data/lib/rubocop/lsp.rb +29 -0
- data/lib/rubocop/magic_comment.rb +1 -1
- data/lib/rubocop/options.rb +11 -0
- data/lib/rubocop/path_util.rb +6 -2
- data/lib/rubocop/rspec/cop_helper.rb +8 -2
- data/lib/rubocop/rspec/expect_offense.rb +16 -8
- data/lib/rubocop/rspec/shared_contexts.rb +49 -18
- data/lib/rubocop/rspec/support.rb +2 -1
- data/lib/rubocop/runner.rb +12 -2
- data/lib/rubocop/target_finder.rb +84 -78
- data/lib/rubocop/target_ruby.rb +82 -80
- data/lib/rubocop/version.rb +19 -4
- data/lib/rubocop.rb +1 -0
- metadata +10 -6
@@ -8,6 +8,12 @@ module RuboCop
|
|
8
8
|
# This cop identifies places where `do_something(*args, &block)`
|
9
9
|
# can be replaced by `do_something(...)`.
|
10
10
|
#
|
11
|
+
# In Ruby 3.1, anonymous block forwarding has been added.
|
12
|
+
#
|
13
|
+
# This cop identifies places where `do_something(&block)` can be replaced
|
14
|
+
# by `do_something(&)`; if desired, this functionality can be disabled
|
15
|
+
# by setting `UseAnonymousForwarding: false`.
|
16
|
+
#
|
11
17
|
# In Ruby 3.2, anonymous args/kwargs forwarding has been added.
|
12
18
|
#
|
13
19
|
# This cop also identifies places where `use_args(*args)`/`use_kwargs(**kwargs)` can be
|
@@ -41,22 +47,25 @@ module RuboCop
|
|
41
47
|
#
|
42
48
|
# @example UseAnonymousForwarding: true (default, only relevant for Ruby >= 3.2)
|
43
49
|
# # bad
|
44
|
-
# def foo(*args, **kwargs)
|
50
|
+
# def foo(*args, **kwargs, &block)
|
45
51
|
# args_only(*args)
|
46
52
|
# kwargs_only(**kwargs)
|
53
|
+
# block_only(&block)
|
47
54
|
# end
|
48
55
|
#
|
49
56
|
# # good
|
50
|
-
# def foo(*,
|
57
|
+
# def foo(*, **, &)
|
51
58
|
# args_only(*)
|
52
59
|
# kwargs_only(**)
|
60
|
+
# block_only(&)
|
53
61
|
# end
|
54
62
|
#
|
55
63
|
# @example UseAnonymousForwarding: false (only relevant for Ruby >= 3.2)
|
56
64
|
# # good
|
57
|
-
# def foo(*args, **kwargs)
|
65
|
+
# def foo(*args, **kwargs, &block)
|
58
66
|
# args_only(*args)
|
59
67
|
# kwargs_only(**kwargs)
|
68
|
+
# block_only(&block)
|
60
69
|
# end
|
61
70
|
#
|
62
71
|
# @example AllowOnlyRestArgument: true (default, only relevant for Ruby < 3.2)
|
@@ -179,9 +188,12 @@ module RuboCop
|
|
179
188
|
|
180
189
|
send_classifications.each do |send_node, _c, forward_rest, forward_kwrest, forward_block_arg| # rubocop:disable Layout/LineLength
|
181
190
|
if !forward_rest && !forward_kwrest
|
182
|
-
|
183
|
-
|
184
|
-
|
191
|
+
# Prevents `anonymous block parameter is also used within block (SyntaxError)` occurs
|
192
|
+
# in Ruby 3.3.0.
|
193
|
+
if outside_block?(forward_block_arg)
|
194
|
+
register_forward_block_arg_offense(!forward_rest, node.arguments, block_arg)
|
195
|
+
register_forward_block_arg_offense(!forward_rest, send_node, forward_block_arg)
|
196
|
+
end
|
185
197
|
registered_block_arg_offense = true
|
186
198
|
break
|
187
199
|
else
|
@@ -196,12 +208,13 @@ module RuboCop
|
|
196
208
|
end
|
197
209
|
# rubocop:enable Metrics/MethodLength
|
198
210
|
|
211
|
+
# rubocop:disable Metrics/AbcSize, Metrics/MethodLength
|
199
212
|
def add_post_ruby_32_offenses(def_node, send_classifications, forwardable_args)
|
200
213
|
return unless use_anonymous_forwarding?
|
201
214
|
|
202
|
-
rest_arg, kwrest_arg,
|
215
|
+
rest_arg, kwrest_arg, block_arg = *forwardable_args
|
203
216
|
|
204
|
-
send_classifications.each do |send_node, _c, forward_rest, forward_kwrest,
|
217
|
+
send_classifications.each do |send_node, _c, forward_rest, forward_kwrest, forward_block_arg| # rubocop:disable Layout/LineLength
|
205
218
|
if outside_block?(forward_rest)
|
206
219
|
register_forward_args_offense(def_node.arguments, rest_arg)
|
207
220
|
register_forward_args_offense(send_node, forward_rest)
|
@@ -211,8 +224,16 @@ module RuboCop
|
|
211
224
|
register_forward_kwargs_offense(!forward_rest, def_node.arguments, kwrest_arg)
|
212
225
|
register_forward_kwargs_offense(!forward_rest, send_node, forward_kwrest)
|
213
226
|
end
|
227
|
+
|
228
|
+
# Prevents `anonymous block parameter is also used within block (SyntaxError)` occurs
|
229
|
+
# in Ruby 3.3.0.
|
230
|
+
if outside_block?(forward_block_arg)
|
231
|
+
register_forward_block_arg_offense(!forward_rest, def_node.arguments, block_arg)
|
232
|
+
register_forward_block_arg_offense(!forward_rest, send_node, forward_block_arg)
|
233
|
+
end
|
214
234
|
end
|
215
235
|
end
|
236
|
+
# rubocop:enable Metrics/AbcSize, Metrics/MethodLength
|
216
237
|
|
217
238
|
def non_splat_or_block_pass_lvar_references(body)
|
218
239
|
body.each_descendant(:lvar, :lvasgn).filter_map do |lvar|
|
@@ -54,9 +54,9 @@ module RuboCop
|
|
54
54
|
end
|
55
55
|
|
56
56
|
def on_send(node)
|
57
|
-
|
58
|
-
|
59
|
-
)
|
57
|
+
return unless (first_argument = node.first_argument)
|
58
|
+
|
59
|
+
add_offense(first_argument, message: format(MSG, class_var: first_argument.source))
|
60
60
|
end
|
61
61
|
end
|
62
62
|
end
|
@@ -19,9 +19,7 @@ module RuboCop
|
|
19
19
|
# @example
|
20
20
|
# # bad
|
21
21
|
# array.reject(&:nil?)
|
22
|
-
# array.delete_if(&:nil?)
|
23
22
|
# array.reject { |e| e.nil? }
|
24
|
-
# array.delete_if { |e| e.nil? }
|
25
23
|
# array.select { |e| !e.nil? }
|
26
24
|
# array.grep_v(nil)
|
27
25
|
# array.grep_v(NilClass)
|
@@ -31,7 +29,9 @@ module RuboCop
|
|
31
29
|
#
|
32
30
|
# # bad
|
33
31
|
# hash.reject!(&:nil?)
|
32
|
+
# array.delete_if(&:nil?)
|
34
33
|
# hash.reject! { |k, v| v.nil? }
|
34
|
+
# array.delete_if { |e| e.nil? }
|
35
35
|
# hash.select! { |k, v| !v.nil? }
|
36
36
|
#
|
37
37
|
# # good
|
@@ -127,7 +127,7 @@ module RuboCop
|
|
127
127
|
end
|
128
128
|
|
129
129
|
def good_method_name(node)
|
130
|
-
if node.bang_method?
|
130
|
+
if node.bang_method? || node.method?(:delete_if)
|
131
131
|
'compact!'
|
132
132
|
else
|
133
133
|
'compact'
|
@@ -1,5 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require_relative '../../directive_comment'
|
4
|
+
|
3
5
|
module RuboCop
|
4
6
|
module Cop
|
5
7
|
module Style
|
@@ -49,8 +51,9 @@ module RuboCop
|
|
49
51
|
KEYWORDS = %w[begin class def end module].freeze
|
50
52
|
KEYWORD_REGEXES = KEYWORDS.map { |w| /^\s*#{w}\s/ }.freeze
|
51
53
|
|
52
|
-
ALLOWED_COMMENTS = %w[:nodoc: :yields:
|
53
|
-
ALLOWED_COMMENT_REGEXES = ALLOWED_COMMENTS.map { |c| /#\s*#{c}/ }
|
54
|
+
ALLOWED_COMMENTS = %w[:nodoc: :yields:].freeze
|
55
|
+
ALLOWED_COMMENT_REGEXES = (ALLOWED_COMMENTS.map { |c| /#\s*#{c}/ } +
|
56
|
+
[DirectiveComment::DIRECTIVE_COMMENT_REGEXP]).freeze
|
54
57
|
|
55
58
|
REGEXP = /(?<keyword>\S+).*#/.freeze
|
56
59
|
|
@@ -115,8 +115,8 @@ module RuboCop
|
|
115
115
|
end
|
116
116
|
|
117
117
|
# Check for `if` and `case` statements where each branch is used for
|
118
|
-
#
|
119
|
-
# condition can be used instead.
|
118
|
+
# both the assignment and comparison of the same variable
|
119
|
+
# when using the return of the condition can be used instead.
|
120
120
|
#
|
121
121
|
# @example EnforcedStyle: assign_to_condition (default)
|
122
122
|
# # bad
|
@@ -460,9 +460,8 @@ module RuboCop
|
|
460
460
|
|
461
461
|
def assignment(node)
|
462
462
|
*_, condition = *node
|
463
|
-
|
464
|
-
|
465
|
-
condition.source_range.begin_pos)
|
463
|
+
|
464
|
+
node.source_range.begin.join(condition.source_range.begin)
|
466
465
|
end
|
467
466
|
|
468
467
|
def correct_if_branches(corrector, cop, node)
|
@@ -28,18 +28,27 @@ module RuboCop
|
|
28
28
|
def on_new_investigation
|
29
29
|
return if notice.empty? || notice_found?(processed_source)
|
30
30
|
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
31
|
+
verify_autocorrect_notice!
|
32
|
+
message = format(MSG, notice: notice)
|
33
|
+
if processed_source.blank?
|
34
|
+
add_global_offense(message)
|
35
|
+
else
|
36
|
+
offense_range = source_range(processed_source.buffer, 1, 0)
|
37
|
+
add_offense(offense_range, message: message) do |corrector|
|
38
|
+
autocorrect(corrector)
|
39
|
+
end
|
38
40
|
end
|
39
41
|
end
|
40
42
|
|
41
43
|
private
|
42
44
|
|
45
|
+
def autocorrect(corrector)
|
46
|
+
token = insert_notice_before(processed_source)
|
47
|
+
range = token.nil? ? range_between(0, 0) : token.pos
|
48
|
+
|
49
|
+
corrector.insert_before(range, "#{autocorrect_notice}\n")
|
50
|
+
end
|
51
|
+
|
43
52
|
def notice
|
44
53
|
cop_config['Notice']
|
45
54
|
end
|
@@ -48,10 +57,6 @@ module RuboCop
|
|
48
57
|
cop_config['AutocorrectNotice']
|
49
58
|
end
|
50
59
|
|
51
|
-
def offense_range
|
52
|
-
source_range(processed_source.buffer, 1, 0)
|
53
|
-
end
|
54
|
-
|
55
60
|
def verify_autocorrect_notice!
|
56
61
|
raise Warning, AUTOCORRECT_EMPTY_WARNING if autocorrect_notice.empty?
|
57
62
|
|
@@ -155,6 +155,8 @@ module RuboCop
|
|
155
155
|
|
156
156
|
def check_line(node, code)
|
157
157
|
line_node = node.last_argument
|
158
|
+
return if line_node.variable? || (line_node.send_type? && !line_node.method?(:+))
|
159
|
+
|
158
160
|
line_diff = line_difference(line_node, code)
|
159
161
|
if line_diff.zero?
|
160
162
|
add_offense_for_same_line(node, line_node)
|
@@ -38,12 +38,13 @@ module RuboCop
|
|
38
38
|
PATTERN
|
39
39
|
|
40
40
|
def on_send(node)
|
41
|
+
return unless (receiver = node.receiver)
|
41
42
|
return unless (regexp = exact_regexp_match(node))
|
42
43
|
|
43
44
|
parsed_regexp = Regexp::Parser.parse(regexp)
|
44
45
|
return unless exact_match_pattern?(parsed_regexp)
|
45
46
|
|
46
|
-
prefer = "#{
|
47
|
+
prefer = "#{receiver.source} #{new_method(node)} '#{parsed_regexp[1].text}'"
|
47
48
|
|
48
49
|
add_offense(node, message: format(MSG, prefer: prefer)) do |corrector|
|
49
50
|
corrector.replace(node, prefer)
|
@@ -25,27 +25,27 @@ module RuboCop
|
|
25
25
|
#
|
26
26
|
# @example EnforcedStyle: format (default)
|
27
27
|
# # bad
|
28
|
-
# puts sprintf('%10s', '
|
29
|
-
# puts '%10s' % '
|
28
|
+
# puts sprintf('%10s', 'foo')
|
29
|
+
# puts '%10s' % 'foo'
|
30
30
|
#
|
31
31
|
# # good
|
32
|
-
# puts format('%10s', '
|
32
|
+
# puts format('%10s', 'foo')
|
33
33
|
#
|
34
34
|
# @example EnforcedStyle: sprintf
|
35
35
|
# # bad
|
36
|
-
# puts format('%10s', '
|
37
|
-
# puts '%10s' % '
|
36
|
+
# puts format('%10s', 'foo')
|
37
|
+
# puts '%10s' % 'foo'
|
38
38
|
#
|
39
39
|
# # good
|
40
|
-
# puts sprintf('%10s', '
|
40
|
+
# puts sprintf('%10s', 'foo')
|
41
41
|
#
|
42
42
|
# @example EnforcedStyle: percent
|
43
43
|
# # bad
|
44
|
-
# puts format('%10s', '
|
45
|
-
# puts sprintf('%10s', '
|
44
|
+
# puts format('%10s', 'foo')
|
45
|
+
# puts sprintf('%10s', 'foo')
|
46
46
|
#
|
47
47
|
# # good
|
48
|
-
# puts '%10s' % '
|
48
|
+
# puts '%10s' % 'foo'
|
49
49
|
#
|
50
50
|
class FormatString < Base
|
51
51
|
include ConfigurableEnforcedStyle
|
@@ -40,7 +40,7 @@ module RuboCop
|
|
40
40
|
|
41
41
|
MSG = 'Use `%<prefer>s` instead of `%<current>s`.'
|
42
42
|
UNUSED_BLOCK_ARG_MSG = "#{MSG.chop} and remove the unused `%<unused_code>s` block argument."
|
43
|
-
ARRAY_CONVERTER_METHODS = %i[assoc flatten rassoc sort sort_by to_a].freeze
|
43
|
+
ARRAY_CONVERTER_METHODS = %i[assoc chunk flatten rassoc sort sort_by to_a].freeze
|
44
44
|
|
45
45
|
# @!method kv_each(node)
|
46
46
|
def_node_matcher :kv_each, <<~PATTERN
|
@@ -195,6 +195,7 @@ module RuboCop
|
|
195
195
|
acceptable_19_syntax_symbol?(pair.key.source)
|
196
196
|
end
|
197
197
|
|
198
|
+
# rubocop:disable Metrics/CyclomaticComplexity
|
198
199
|
def acceptable_19_syntax_symbol?(sym_name)
|
199
200
|
sym_name.delete_prefix!(':')
|
200
201
|
|
@@ -209,9 +210,12 @@ module RuboCop
|
|
209
210
|
# Most hash keys can be matched against a simple regex.
|
210
211
|
return true if /\A[_a-z]\w*[?!]?\z/i.match?(sym_name)
|
211
212
|
|
212
|
-
|
213
|
-
|
213
|
+
return false if target_ruby_version <= 2.1
|
214
|
+
|
215
|
+
(sym_name.start_with?("'") && sym_name.end_with?("'")) ||
|
216
|
+
(sym_name.start_with?('"') && sym_name.end_with?('"'))
|
214
217
|
end
|
218
|
+
# rubocop:enable Metrics/CyclomaticComplexity
|
215
219
|
|
216
220
|
def check(pairs, delim, msg)
|
217
221
|
pairs.each do |pair|
|
@@ -76,9 +76,9 @@ module RuboCop
|
|
76
76
|
PATTERN
|
77
77
|
|
78
78
|
def on_send(node)
|
79
|
-
inverse_candidate?(node) do |
|
79
|
+
inverse_candidate?(node) do |method_call, lhs, method, rhs|
|
80
80
|
return unless inverse_methods.key?(method)
|
81
|
-
return if negated?(node)
|
81
|
+
return if negated?(node) || relational_comparison_with_safe_navigation?(method_call)
|
82
82
|
return if part_of_ignored_node?(node)
|
83
83
|
return if possible_class_hierarchy_check?(lhs, rhs, method)
|
84
84
|
|
@@ -155,16 +155,16 @@ module RuboCop
|
|
155
155
|
node.parent.respond_to?(:method?) && node.parent.method?(:!)
|
156
156
|
end
|
157
157
|
|
158
|
+
def relational_comparison_with_safe_navigation?(node)
|
159
|
+
node.csend_type? && CLASS_COMPARISON_METHODS.include?(node.method_name)
|
160
|
+
end
|
161
|
+
|
158
162
|
def not_to_receiver(node, method_call)
|
159
|
-
|
160
|
-
node.loc.selector.begin_pos,
|
161
|
-
method_call.source_range.begin_pos)
|
163
|
+
node.loc.selector.begin.join(method_call.source_range.begin)
|
162
164
|
end
|
163
165
|
|
164
166
|
def end_parentheses(node, method_call)
|
165
|
-
|
166
|
-
method_call.source_range.end_pos,
|
167
|
-
node.source_range.end_pos)
|
167
|
+
method_call.source_range.end.join(node.source_range.end)
|
168
168
|
end
|
169
169
|
|
170
170
|
# When comparing classes, `!(Integer < Numeric)` is not the same as
|
@@ -32,12 +32,14 @@ module RuboCop
|
|
32
32
|
# foo unless x != y
|
33
33
|
# foo unless x >= 10
|
34
34
|
# foo unless x.even?
|
35
|
+
# foo unless odd?
|
35
36
|
#
|
36
37
|
# # good
|
37
38
|
# foo if bar
|
38
39
|
# foo if x == y
|
39
40
|
# foo if x < 10
|
40
41
|
# foo if x.odd?
|
42
|
+
# foo if even?
|
41
43
|
#
|
42
44
|
# # bad (complex condition)
|
43
45
|
# foo unless x != y || x.even?
|
@@ -99,12 +101,15 @@ module RuboCop
|
|
99
101
|
end
|
100
102
|
end
|
101
103
|
|
102
|
-
def preferred_send_condition(node)
|
103
|
-
receiver_source = node.receiver
|
104
|
+
def preferred_send_condition(node) # rubocop:disable Metrics/CyclomaticComplexity
|
105
|
+
receiver_source = node.receiver&.source
|
104
106
|
return receiver_source if node.method?(:!)
|
105
107
|
|
108
|
+
# receiver may be implicit (self)
|
109
|
+
dotted_receiver_source = receiver_source ? "#{receiver_source}." : ''
|
110
|
+
|
106
111
|
inverse_method_name = inverse_methods[node.method_name]
|
107
|
-
return "#{
|
112
|
+
return "#{dotted_receiver_source}#{inverse_method_name}" unless node.arguments?
|
108
113
|
|
109
114
|
argument_list = node.arguments.map(&:source).join(', ')
|
110
115
|
if node.operator_method?
|
@@ -112,10 +117,10 @@ module RuboCop
|
|
112
117
|
end
|
113
118
|
|
114
119
|
if node.parenthesized?
|
115
|
-
return "#{
|
120
|
+
return "#{dotted_receiver_source}#{inverse_method_name}(#{argument_list})"
|
116
121
|
end
|
117
122
|
|
118
|
-
"#{
|
123
|
+
"#{dotted_receiver_source}#{inverse_method_name} #{argument_list}"
|
119
124
|
end
|
120
125
|
|
121
126
|
def preferred_logical_condition(node)
|
@@ -116,20 +116,17 @@ module RuboCop
|
|
116
116
|
def truthy_branch_for_guard?(node)
|
117
117
|
if_node = node.left_sibling
|
118
118
|
|
119
|
-
if if_node.if?
|
120
|
-
if_node.
|
121
|
-
|
122
|
-
if_node.if_branch.
|
119
|
+
if if_node.if?
|
120
|
+
if_node.if_branch.arguments.any?
|
121
|
+
else
|
122
|
+
if_node.if_branch.arguments.none?
|
123
123
|
end
|
124
124
|
end
|
125
125
|
|
126
126
|
def range(node)
|
127
|
-
buffer = node.source_range.source_buffer
|
128
127
|
map_node = node.receiver.send_node
|
129
|
-
begin_pos = map_node.loc.selector.begin_pos
|
130
|
-
end_pos = node.source_range.end_pos
|
131
128
|
|
132
|
-
|
129
|
+
map_node.loc.selector.join(node.source_range.end)
|
133
130
|
end
|
134
131
|
end
|
135
132
|
end
|
@@ -0,0 +1,175 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module Style
|
6
|
+
# Checks for usages of `each` with `<<`, `push`, or `append` which
|
7
|
+
# can be replaced by `map`.
|
8
|
+
#
|
9
|
+
# If `PreferredMethods` is configured for `map` in `Style/CollectionMethods`,
|
10
|
+
# this cop uses the specified method for replacement.
|
11
|
+
#
|
12
|
+
# NOTE: The return value of `Enumerable#each` is `self`, whereas the
|
13
|
+
# return value of `Enumerable#map` is an `Array`. They are not autocorrected
|
14
|
+
# when a return value could be used because these types differ.
|
15
|
+
#
|
16
|
+
# NOTE: It only detects when the mapping destination is a local variable
|
17
|
+
# initialized as an empty array and referred to only by the pushing operation.
|
18
|
+
# This is because, if not, it's challenging to statically guarantee that the
|
19
|
+
# mapping destination variable remains an empty array:
|
20
|
+
#
|
21
|
+
# [source,ruby]
|
22
|
+
# ----
|
23
|
+
# @dest = []
|
24
|
+
# src.each { |e| @dest << e * 2 } # `src` method may mutate `@dest`
|
25
|
+
#
|
26
|
+
# dest = []
|
27
|
+
# src.each { |e| dest << transform(e, dest) } # `transform` method may mutate `dest`
|
28
|
+
# ----
|
29
|
+
#
|
30
|
+
# @safety
|
31
|
+
# This cop is unsafe because not all objects that have an `each`
|
32
|
+
# method also have a `map` method (e.g. `ENV`). Additionally, for calls
|
33
|
+
# with a block, not all objects that have a `map` method return an array
|
34
|
+
# (e.g. `Enumerator::Lazy`).
|
35
|
+
#
|
36
|
+
# @example
|
37
|
+
# # bad
|
38
|
+
# dest = []
|
39
|
+
# src.each { |e| dest << e * 2 }
|
40
|
+
# dest
|
41
|
+
#
|
42
|
+
# # good
|
43
|
+
# dest = src.map { |e| e * 2 }
|
44
|
+
#
|
45
|
+
# # good - contains another operation
|
46
|
+
# dest = []
|
47
|
+
# src.each { |e| dest << e * 2; puts e }
|
48
|
+
# dest
|
49
|
+
#
|
50
|
+
class MapIntoArray < Base
|
51
|
+
include RangeHelp
|
52
|
+
extend AutoCorrector
|
53
|
+
|
54
|
+
MSG = 'Use `%<new_method_name>s` instead of `each` to map elements into an array.'
|
55
|
+
|
56
|
+
# @!method each_block_with_push?(node)
|
57
|
+
def_node_matcher :each_block_with_push?, <<-PATTERN
|
58
|
+
[
|
59
|
+
^({begin kwbegin} ...)
|
60
|
+
({block numblock} (send _ :each) _
|
61
|
+
(send (lvar _) {:<< :push :append} _))
|
62
|
+
]
|
63
|
+
PATTERN
|
64
|
+
|
65
|
+
# @!method empty_array_asgn?(node)
|
66
|
+
def_node_matcher :empty_array_asgn?, '(lvasgn _ (array))'
|
67
|
+
|
68
|
+
# @!method lvar_ref?(node, name)
|
69
|
+
def_node_matcher :lvar_ref?, '(lvar %1)'
|
70
|
+
|
71
|
+
def self.joining_forces
|
72
|
+
VariableForce
|
73
|
+
end
|
74
|
+
|
75
|
+
def after_leaving_scope(scope, _variable_table)
|
76
|
+
(@scopes ||= []) << scope
|
77
|
+
end
|
78
|
+
|
79
|
+
def on_block(node)
|
80
|
+
return unless each_block_with_push?(node)
|
81
|
+
|
82
|
+
dest_var = find_dest_var(node)
|
83
|
+
return unless (asgn = find_closest_assignment(node, dest_var))
|
84
|
+
return unless empty_array_asgn?(asgn)
|
85
|
+
return unless dest_used_only_for_mapping?(node, dest_var, asgn)
|
86
|
+
|
87
|
+
register_offense(node, dest_var, asgn)
|
88
|
+
end
|
89
|
+
|
90
|
+
alias on_numblock on_block
|
91
|
+
|
92
|
+
private
|
93
|
+
|
94
|
+
def find_dest_var(block)
|
95
|
+
node = block.body.receiver
|
96
|
+
name = node.children.first
|
97
|
+
|
98
|
+
candidates = @scopes.lazy.filter_map { |s| s.variables[name] }
|
99
|
+
candidates.find { |v| v.references.any? { |n| n.node.equal?(node) } }
|
100
|
+
end
|
101
|
+
|
102
|
+
def find_closest_assignment(block, dest_var)
|
103
|
+
dest_var.assignments.reverse_each.lazy.map(&:node).find do |node|
|
104
|
+
node.source_range.end_pos < block.source_range.begin_pos
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
def dest_used_only_for_mapping?(block, dest_var, asgn)
|
109
|
+
range = asgn.source_range.join(block.source_range)
|
110
|
+
|
111
|
+
asgn.parent.equal?(block.parent) &&
|
112
|
+
dest_var.references.one? { |r| range.contains?(r.node.source_range) } &&
|
113
|
+
dest_var.assignments.one? { |a| range.contains?(a.node.source_range) }
|
114
|
+
end
|
115
|
+
|
116
|
+
def register_offense(block, dest_var, asgn)
|
117
|
+
add_offense(block, message: format(MSG, new_method_name: new_method_name)) do |corrector|
|
118
|
+
next if return_value_used?(block)
|
119
|
+
|
120
|
+
corrector.replace(block.send_node.selector, new_method_name)
|
121
|
+
remove_assignment(corrector, asgn)
|
122
|
+
correct_push_node(corrector, block.body)
|
123
|
+
correct_return_value_handling(corrector, block, dest_var)
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
def new_method_name
|
128
|
+
default = 'map'
|
129
|
+
alternative = config.for_cop('Style/CollectionMethods').dig('PreferredMethods', default)
|
130
|
+
alternative || default
|
131
|
+
end
|
132
|
+
|
133
|
+
def return_value_used?(node)
|
134
|
+
parent = node.parent
|
135
|
+
|
136
|
+
case parent&.type
|
137
|
+
when nil
|
138
|
+
false
|
139
|
+
when :begin, :kwbegin
|
140
|
+
!node.right_sibling && return_value_used?(parent)
|
141
|
+
when :block, :numblock
|
142
|
+
!parent.void_context?
|
143
|
+
else
|
144
|
+
true
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
def remove_assignment(corrector, asgn)
|
149
|
+
range = range_with_surrounding_space(asgn.source_range, side: :right)
|
150
|
+
range = range_with_surrounding_space(range, side: :right, newlines: false)
|
151
|
+
|
152
|
+
corrector.remove(range)
|
153
|
+
end
|
154
|
+
|
155
|
+
def correct_push_node(corrector, push_node)
|
156
|
+
range = push_node.source_range
|
157
|
+
arg_range = push_node.first_argument.source_range
|
158
|
+
|
159
|
+
corrector.remove(range_between(range.begin_pos, arg_range.begin_pos))
|
160
|
+
corrector.remove(range_between(arg_range.end_pos, range.end_pos))
|
161
|
+
end
|
162
|
+
|
163
|
+
def correct_return_value_handling(corrector, block, dest_var)
|
164
|
+
next_node = block.right_sibling
|
165
|
+
|
166
|
+
if lvar_ref?(next_node, dest_var.name)
|
167
|
+
corrector.remove(range_with_surrounding_space(next_node.source_range, side: :left))
|
168
|
+
end
|
169
|
+
|
170
|
+
corrector.insert_before(block, "#{dest_var.name} = ")
|
171
|
+
end
|
172
|
+
end
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|
@@ -55,7 +55,7 @@ module RuboCop
|
|
55
55
|
message = format(MSG, method: map_node.loc.selector.source, dot: to_h_node.loc.dot.source)
|
56
56
|
add_offense(map_node.loc.selector, message: message) do |corrector|
|
57
57
|
# If the `to_h` call already has a block, do not autocorrect.
|
58
|
-
next if to_h_node.
|
58
|
+
next if to_h_node.block_literal?
|
59
59
|
|
60
60
|
autocorrect(corrector, to_h_node, map_node)
|
61
61
|
end
|
@@ -44,7 +44,7 @@ module RuboCop
|
|
44
44
|
message = format(MSG, method: map_node.loc.selector.source)
|
45
45
|
add_offense(map_node.loc.selector, message: message) do |corrector|
|
46
46
|
# If the `to_set` call already has a block, do not autocorrect.
|
47
|
-
next if to_set_node.
|
47
|
+
next if to_set_node.block_literal?
|
48
48
|
|
49
49
|
autocorrect(corrector, to_set_node, map_node)
|
50
50
|
end
|
@@ -132,7 +132,7 @@ module RuboCop
|
|
132
132
|
call_in_match_pattern?(node) ||
|
133
133
|
hash_literal_in_arguments?(node) ||
|
134
134
|
node.descendants.any? do |n|
|
135
|
-
n.forwarded_args_type? || n.block_type? ||
|
135
|
+
n.forwarded_args_type? || n.block_type? || n.numblock_type? ||
|
136
136
|
ambiguous_literal?(n) || logical_operator?(n)
|
137
137
|
end
|
138
138
|
end
|