rubocop 1.22.3 → 1.26.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 +110 -19
- 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 +2 -1
- data/lib/rubocop/config_loader_resolver.rb +1 -1
- data/lib/rubocop/cop/badge.rb +7 -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/date_assignment.rb +2 -10
- data/lib/rubocop/cop/gemspec/duplicated_assignment.rb +1 -10
- data/lib/rubocop/cop/gemspec/require_mfa.rb +145 -0
- data/lib/rubocop/cop/gemspec/required_ruby_version.rb +10 -3
- data/lib/rubocop/cop/gemspec/ruby_version_globals_usage.rb +3 -10
- data/lib/rubocop/cop/generator.rb +7 -11
- data/lib/rubocop/cop/internal_affairs/redundant_context_config_parameter.rb +46 -0
- 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 +2 -0
- data/lib/rubocop/cop/layout/argument_alignment.rb +36 -9
- data/lib/rubocop/cop/layout/case_indentation.rb +1 -1
- 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_line_between_defs.rb +22 -1
- 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/multiline_operation_indentation.rb +7 -8
- 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/layout/space_in_lambda_literal.rb +11 -5
- data/lib/rubocop/cop/lint/ambiguous_range.rb +2 -2
- 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/inherit_exception.rb +19 -28
- data/lib/rubocop/cop/lint/number_conversion.rb +5 -2
- data/lib/rubocop/cop/lint/parentheses_as_grouped_expression.rb +10 -6
- data/lib/rubocop/cop/lint/redundant_dir_glob_sort.rb +5 -0
- data/lib/rubocop/cop/lint/symbol_conversion.rb +3 -2
- data/lib/rubocop/cop/lint/useless_ruby2_keywords.rb +117 -0
- data/lib/rubocop/cop/lint/useless_times.rb +13 -9
- 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/parameter_lists.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/gemspec_help.rb +30 -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/mixin/line_length_help.rb +17 -6
- data/lib/rubocop/cop/mixin/multiline_expression_indentation.rb +3 -1
- data/lib/rubocop/cop/mixin/string_literals_help.rb +1 -5
- data/lib/rubocop/cop/naming/block_forwarding.rb +121 -0
- data/lib/rubocop/cop/naming/file_name.rb +37 -4
- data/lib/rubocop/cop/naming/method_parameter_name.rb +1 -1
- data/lib/rubocop/cop/security/open.rb +11 -1
- data/lib/rubocop/cop/security/yaml_load.rb +9 -3
- 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/def_with_parentheses.rb +16 -11
- data/lib/rubocop/cop/style/documentation.rb +1 -1
- data/lib/rubocop/cop/style/empty_case_condition.rb +10 -0
- data/lib/rubocop/cop/style/empty_method.rb +1 -1
- 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/for.rb +4 -0
- data/lib/rubocop/cop/style/format_string_token.rb +2 -1
- 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/lambda_call.rb +12 -20
- 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/nested_file_dirname.rb +66 -0
- 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/open_struct_use.rb +69 -0
- data/lib/rubocop/cop/style/optional_boolean_parameter.rb +3 -2
- data/lib/rubocop/cop/style/parentheses_around_condition.rb +12 -2
- data/lib/rubocop/cop/style/quoted_symbols.rb +11 -1
- 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/select_by_regexp.rb +6 -1
- data/lib/rubocop/cop/style/single_line_block_params.rb +2 -2
- data/lib/rubocop/cop/style/sole_nested_conditional.rb +53 -13
- data/lib/rubocop/cop/style/string_concatenation.rb +7 -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/style/trailing_comma_in_array_literal.rb +1 -1
- data/lib/rubocop/cop/style/trailing_comma_in_hash_literal.rb +1 -1
- data/lib/rubocop/cop/style/unless_else.rb +4 -0
- data/lib/rubocop/cop/team.rb +1 -1
- data/lib/rubocop/cop/util.rb +9 -1
- data/lib/rubocop/cop/variable_force.rb +1 -5
- data/lib/rubocop/cops_documentation_generator.rb +2 -2
- data/lib/rubocop/formatter/disabled_config_formatter.rb +16 -2
- data/lib/rubocop/formatter/html_formatter.rb +5 -2
- data/lib/rubocop/formatter/json_formatter.rb +4 -1
- data/lib/rubocop/options.rb +14 -3
- data/lib/rubocop/remote_config.rb +2 -4
- data/lib/rubocop/result_cache.rb +1 -1
- data/lib/rubocop/rspec/shared_contexts.rb +4 -0
- data/lib/rubocop/runner.rb +1 -1
- data/lib/rubocop/target_ruby.rb +1 -1
- data/lib/rubocop/version.rb +1 -1
- data/lib/rubocop/yaml_duplication_checker.rb +1 -1
- data/lib/rubocop.rb +12 -0
- metadata +22 -7
@@ -79,6 +79,8 @@ module RuboCop
|
|
79
79
|
when_nodes.each do |when_node|
|
80
80
|
conditions = when_node.conditions
|
81
81
|
|
82
|
+
replace_then_with_line_break(corrector, conditions, when_node)
|
83
|
+
|
82
84
|
next unless conditions.size > 1
|
83
85
|
|
84
86
|
range = range_between(conditions.first.source_range.begin_pos,
|
@@ -97,6 +99,14 @@ module RuboCop
|
|
97
99
|
line_beginning = case_range.adjust(begin_pos: -case_range.column)
|
98
100
|
corrector.insert_before(line_beginning, comments)
|
99
101
|
end
|
102
|
+
|
103
|
+
def replace_then_with_line_break(corrector, conditions, when_node)
|
104
|
+
return unless when_node.parent.parent && when_node.then?
|
105
|
+
|
106
|
+
range = range_between(conditions.last.source_range.end_pos, when_node.loc.begin.end_pos)
|
107
|
+
|
108
|
+
corrector.replace(range, "\n")
|
109
|
+
end
|
100
110
|
end
|
101
111
|
end
|
102
112
|
end
|
@@ -0,0 +1,112 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module Style
|
6
|
+
# Favor `File.(bin)read` convenience methods.
|
7
|
+
#
|
8
|
+
# @example
|
9
|
+
# ## text mode
|
10
|
+
# # bad
|
11
|
+
# File.open(filename).read
|
12
|
+
# File.open(filename, &:read)
|
13
|
+
# File.open(filename) { |f| f.read }
|
14
|
+
# File.open(filename) do |f|
|
15
|
+
# f.read
|
16
|
+
# end
|
17
|
+
# File.open(filename, 'r').read
|
18
|
+
# File.open(filename, 'r', &:read)
|
19
|
+
# File.open(filename, 'r') do |f|
|
20
|
+
# f.read
|
21
|
+
# end
|
22
|
+
#
|
23
|
+
# # good
|
24
|
+
# File.read(filename)
|
25
|
+
#
|
26
|
+
# @example
|
27
|
+
# ## binary mode
|
28
|
+
# # bad
|
29
|
+
# File.open(filename, 'rb').read
|
30
|
+
# File.open(filename, 'rb', &:read)
|
31
|
+
# File.open(filename, 'rb') do |f|
|
32
|
+
# f.read
|
33
|
+
# end
|
34
|
+
#
|
35
|
+
# # good
|
36
|
+
# File.binread(filename)
|
37
|
+
#
|
38
|
+
class FileRead < Base
|
39
|
+
extend AutoCorrector
|
40
|
+
include RangeHelp
|
41
|
+
|
42
|
+
MSG = 'Use `File.%<read_method>s`.'
|
43
|
+
|
44
|
+
RESTRICT_ON_SEND = %i[open].freeze
|
45
|
+
|
46
|
+
READ_FILE_START_TO_FINISH_MODES = %w[r rt rb r+ r+t r+b].to_set.freeze
|
47
|
+
|
48
|
+
# @!method file_open?(node)
|
49
|
+
def_node_matcher :file_open?, <<~PATTERN
|
50
|
+
(send
|
51
|
+
(const {nil? cbase} :File)
|
52
|
+
:open
|
53
|
+
$_
|
54
|
+
(str $%READ_FILE_START_TO_FINISH_MODES)?
|
55
|
+
$(block-pass (sym :read))?
|
56
|
+
)
|
57
|
+
PATTERN
|
58
|
+
|
59
|
+
# @!method send_read?(node)
|
60
|
+
def_node_matcher :send_read?, <<~PATTERN
|
61
|
+
(send _ :read)
|
62
|
+
PATTERN
|
63
|
+
|
64
|
+
# @!method block_read?(node)
|
65
|
+
def_node_matcher :block_read?, <<~PATTERN
|
66
|
+
(block _ (args (arg $_)) (send (lvar $_) :read))
|
67
|
+
PATTERN
|
68
|
+
|
69
|
+
def on_send(node)
|
70
|
+
evidence(node) do |filename, mode, read_node|
|
71
|
+
message = format(MSG, read_method: read_method(mode))
|
72
|
+
|
73
|
+
add_offense(read_node, message: message) do |corrector|
|
74
|
+
range = range_between(node.loc.selector.begin_pos, read_node.loc.expression.end_pos)
|
75
|
+
replacement = "#{read_method(mode)}(#{filename.source})"
|
76
|
+
|
77
|
+
corrector.replace(range, replacement)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
private
|
83
|
+
|
84
|
+
def evidence(node)
|
85
|
+
file_open?(node) do |filename, mode_array, block_pass|
|
86
|
+
read_node?(node, block_pass) do |read_node|
|
87
|
+
yield(filename, mode_array.first || 'r', read_node)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def read_node?(node, block_pass)
|
93
|
+
if block_pass.any?
|
94
|
+
yield(node)
|
95
|
+
elsif file_open_read?(node.parent)
|
96
|
+
yield(node.parent)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def file_open_read?(node)
|
101
|
+
return true if send_read?(node)
|
102
|
+
|
103
|
+
block_read?(node) { |block_arg, read_lvar| block_arg == read_lvar }
|
104
|
+
end
|
105
|
+
|
106
|
+
def read_method(mode)
|
107
|
+
mode.end_with?('b') ? :binread : :read
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
@@ -0,0 +1,124 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module Style
|
6
|
+
# Favor `File.(bin)write` convenience methods.
|
7
|
+
#
|
8
|
+
# @example
|
9
|
+
# ## text mode
|
10
|
+
# # bad
|
11
|
+
# File.open(filename, 'w').write(content)
|
12
|
+
# File.open(filename, 'w') do |f|
|
13
|
+
# f.write(content)
|
14
|
+
# end
|
15
|
+
#
|
16
|
+
# # good
|
17
|
+
# File.write(filename, content)
|
18
|
+
#
|
19
|
+
# @example
|
20
|
+
# ## binary mode
|
21
|
+
# # bad
|
22
|
+
# File.open(filename, 'wb').write(content)
|
23
|
+
# File.open(filename, 'wb') do |f|
|
24
|
+
# f.write(content)
|
25
|
+
# end
|
26
|
+
#
|
27
|
+
# # good
|
28
|
+
# File.binwrite(filename, content)
|
29
|
+
#
|
30
|
+
class FileWrite < Base
|
31
|
+
extend AutoCorrector
|
32
|
+
include RangeHelp
|
33
|
+
|
34
|
+
MSG = 'Use `File.%<write_method>s`.'
|
35
|
+
|
36
|
+
RESTRICT_ON_SEND = %i[open].to_set.freeze
|
37
|
+
|
38
|
+
TRUNCATING_WRITE_MODES = %w[w wt wb w+ w+t w+b].to_set.freeze
|
39
|
+
|
40
|
+
# @!method file_open?(node)
|
41
|
+
def_node_matcher :file_open?, <<~PATTERN
|
42
|
+
(send
|
43
|
+
(const {nil? cbase} :File)
|
44
|
+
:open
|
45
|
+
$_
|
46
|
+
(str $%TRUNCATING_WRITE_MODES)
|
47
|
+
(block-pass (sym :write))?
|
48
|
+
)
|
49
|
+
PATTERN
|
50
|
+
|
51
|
+
# @!method send_write?(node)
|
52
|
+
def_node_matcher :send_write?, <<~PATTERN
|
53
|
+
(send _ :write $_)
|
54
|
+
PATTERN
|
55
|
+
|
56
|
+
# @!method block_write?(node)
|
57
|
+
def_node_matcher :block_write?, <<~PATTERN
|
58
|
+
(block _ (args (arg $_)) (send (lvar $_) :write $_))
|
59
|
+
PATTERN
|
60
|
+
|
61
|
+
def on_send(node)
|
62
|
+
evidence(node) do |filename, mode, content, write_node|
|
63
|
+
message = format(MSG, write_method: write_method(mode))
|
64
|
+
|
65
|
+
add_offense(write_node, message: message) do |corrector|
|
66
|
+
range = range_between(node.loc.selector.begin_pos, write_node.loc.expression.end_pos)
|
67
|
+
replacement = replacement(mode, filename, content, write_node)
|
68
|
+
|
69
|
+
corrector.replace(range, replacement)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def evidence(node)
|
75
|
+
file_open?(node) do |filename, mode|
|
76
|
+
file_open_write?(node.parent) do |content|
|
77
|
+
yield(filename, mode, content, node.parent)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
private
|
83
|
+
|
84
|
+
def file_open_write?(node)
|
85
|
+
content = send_write?(node) || block_write?(node) do |block_arg, lvar, write_arg|
|
86
|
+
write_arg if block_arg == lvar
|
87
|
+
end
|
88
|
+
|
89
|
+
yield(content) if content
|
90
|
+
end
|
91
|
+
|
92
|
+
def write_method(mode)
|
93
|
+
mode.end_with?('b') ? :binwrite : :write
|
94
|
+
end
|
95
|
+
|
96
|
+
def replacement(mode, filename, content, write_node)
|
97
|
+
replacement = "#{write_method(mode)}(#{filename.source}, #{content.source})"
|
98
|
+
|
99
|
+
if heredoc?(write_node)
|
100
|
+
first_argument = write_node.body.first_argument
|
101
|
+
|
102
|
+
<<~REPLACEMENT.chomp
|
103
|
+
#{replacement}
|
104
|
+
#{heredoc_range(first_argument).source}
|
105
|
+
REPLACEMENT
|
106
|
+
else
|
107
|
+
replacement
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
def heredoc?(write_node)
|
112
|
+
write_node.block_type? && (first_argument = write_node.body.first_argument) &&
|
113
|
+
first_argument.respond_to?(:heredoc?) && first_argument.heredoc?
|
114
|
+
end
|
115
|
+
|
116
|
+
def heredoc_range(first_argument)
|
117
|
+
range_between(
|
118
|
+
first_argument.loc.heredoc_body.begin_pos, first_argument.loc.heredoc_end.end_pos
|
119
|
+
)
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
@@ -102,7 +102,8 @@ module RuboCop
|
|
102
102
|
end
|
103
103
|
|
104
104
|
def use_ignored_method?(node)
|
105
|
-
|
105
|
+
send_parent = node.each_ancestor(:send).first
|
106
|
+
send_parent && ignored_method?(send_parent.method_name)
|
106
107
|
end
|
107
108
|
|
108
109
|
def unannotated_format?(node, detected_style)
|
@@ -101,7 +101,8 @@ module RuboCop
|
|
101
101
|
end
|
102
102
|
|
103
103
|
def requires_parens?(node)
|
104
|
-
node.call_type? && node.arguments.any? && !node.parenthesized?
|
104
|
+
(node.call_type? && node.arguments.any? && !node.parenthesized?) ||
|
105
|
+
node.or_type? || node.and_type?
|
105
106
|
end
|
106
107
|
|
107
108
|
def multi_argument(node)
|
@@ -19,6 +19,16 @@ module RuboCop
|
|
19
19
|
# * ruby19_no_mixed_keys - forces use of ruby 1.9 syntax and forbids mixed
|
20
20
|
# syntax hashes
|
21
21
|
#
|
22
|
+
# This cop has `EnforcedShorthandSyntax` option.
|
23
|
+
# It can enforce either the use of the explicit hash value syntax or
|
24
|
+
# the use of Ruby 3.1's hash value shorthand syntax.
|
25
|
+
#
|
26
|
+
# The supported styles are:
|
27
|
+
#
|
28
|
+
# * always - forces use of the 3.1 syntax (e.g. {foo:})
|
29
|
+
# * never - forces use of explicit hash literal value
|
30
|
+
# * either - accepts both shorthand and explicit use of hash literal value
|
31
|
+
#
|
22
32
|
# @example EnforcedStyle: ruby19 (default)
|
23
33
|
# # bad
|
24
34
|
# {:a => 2}
|
@@ -54,8 +64,34 @@ module RuboCop
|
|
54
64
|
# # good
|
55
65
|
# {a: 1, b: 2}
|
56
66
|
# {:c => 3, 'd' => 4}
|
67
|
+
#
|
68
|
+
# @example EnforcedShorthandSyntax: always (default)
|
69
|
+
#
|
70
|
+
# # bad
|
71
|
+
# {foo: foo, bar: bar}
|
72
|
+
#
|
73
|
+
# # good
|
74
|
+
# {foo:, bar:}
|
75
|
+
#
|
76
|
+
# @example EnforcedShorthandSyntax: never
|
77
|
+
#
|
78
|
+
# # bad
|
79
|
+
# {foo:, bar:}
|
80
|
+
#
|
81
|
+
# # good
|
82
|
+
# {foo: foo, bar: bar}
|
83
|
+
#
|
84
|
+
# @example EnforcedShorthandSyntax: either
|
85
|
+
#
|
86
|
+
# # good
|
87
|
+
# {foo: foo, bar: bar}
|
88
|
+
#
|
89
|
+
# # good
|
90
|
+
# {foo:, bar:}
|
91
|
+
#
|
57
92
|
class HashSyntax < Base
|
58
93
|
include ConfigurableEnforcedStyle
|
94
|
+
include HashShorthandSyntax
|
59
95
|
include RangeHelp
|
60
96
|
extend AutoCorrector
|
61
97
|
|
@@ -30,13 +30,13 @@ module RuboCop
|
|
30
30
|
# @!method on_bad_each_with_object(node)
|
31
31
|
def_node_matcher :on_bad_each_with_object, <<~PATTERN
|
32
32
|
(block
|
33
|
-
(
|
33
|
+
(call !#array_receiver? :each_with_object (hash))
|
34
34
|
(args
|
35
35
|
(mlhs
|
36
36
|
(arg $_)
|
37
37
|
(arg _val))
|
38
38
|
(arg _memo))
|
39
|
-
(
|
39
|
+
(call (lvar _memo) :[]= $!`_memo $(lvar _val)))
|
40
40
|
PATTERN
|
41
41
|
|
42
42
|
# @!method on_bad_hash_brackets_map(node)
|
@@ -45,7 +45,7 @@ module RuboCop
|
|
45
45
|
(const _ :Hash)
|
46
46
|
:[]
|
47
47
|
(block
|
48
|
-
(
|
48
|
+
(call !#array_receiver? {:map :collect})
|
49
49
|
(args
|
50
50
|
(arg $_)
|
51
51
|
(arg _val))
|
@@ -54,9 +54,9 @@ module RuboCop
|
|
54
54
|
|
55
55
|
# @!method on_bad_map_to_h(node)
|
56
56
|
def_node_matcher :on_bad_map_to_h, <<~PATTERN
|
57
|
-
(
|
57
|
+
(call
|
58
58
|
(block
|
59
|
-
(
|
59
|
+
(call !#array_receiver? {:map :collect})
|
60
60
|
(args
|
61
61
|
(arg $_)
|
62
62
|
(arg _val))
|
@@ -67,7 +67,7 @@ module RuboCop
|
|
67
67
|
# @!method on_bad_to_h(node)
|
68
68
|
def_node_matcher :on_bad_to_h, <<~PATTERN
|
69
69
|
(block
|
70
|
-
(
|
70
|
+
(call !#array_receiver? :to_h)
|
71
71
|
(args
|
72
72
|
(arg $_)
|
73
73
|
(arg _val))
|
@@ -30,13 +30,13 @@ module RuboCop
|
|
30
30
|
# @!method on_bad_each_with_object(node)
|
31
31
|
def_node_matcher :on_bad_each_with_object, <<~PATTERN
|
32
32
|
(block
|
33
|
-
(
|
33
|
+
(call !#array_receiver? :each_with_object (hash))
|
34
34
|
(args
|
35
35
|
(mlhs
|
36
36
|
(arg _key)
|
37
37
|
(arg $_))
|
38
38
|
(arg _memo))
|
39
|
-
(
|
39
|
+
(call (lvar _memo) :[]= $(lvar _key) $!`_memo))
|
40
40
|
PATTERN
|
41
41
|
|
42
42
|
# @!method on_bad_hash_brackets_map(node)
|
@@ -45,7 +45,7 @@ module RuboCop
|
|
45
45
|
(const _ :Hash)
|
46
46
|
:[]
|
47
47
|
(block
|
48
|
-
(
|
48
|
+
(call !#array_receiver? {:map :collect})
|
49
49
|
(args
|
50
50
|
(arg _key)
|
51
51
|
(arg $_))
|
@@ -54,9 +54,9 @@ module RuboCop
|
|
54
54
|
|
55
55
|
# @!method on_bad_map_to_h(node)
|
56
56
|
def_node_matcher :on_bad_map_to_h, <<~PATTERN
|
57
|
-
(
|
57
|
+
(call
|
58
58
|
(block
|
59
|
-
(
|
59
|
+
(call !#array_receiver? {:map :collect})
|
60
60
|
(args
|
61
61
|
(arg _key)
|
62
62
|
(arg $_))
|
@@ -67,7 +67,7 @@ module RuboCop
|
|
67
67
|
# @!method on_bad_to_h(node)
|
68
68
|
def_node_matcher :on_bad_to_h, <<~PATTERN
|
69
69
|
(block
|
70
|
-
(
|
70
|
+
(call !#array_receiver? :to_h)
|
71
71
|
(args
|
72
72
|
(arg _key)
|
73
73
|
(arg $_))
|
@@ -80,11 +80,19 @@ module RuboCop
|
|
80
80
|
private
|
81
81
|
|
82
82
|
def autocorrect(corrector, node)
|
83
|
+
if then?(node)
|
84
|
+
# If the nested `if` is a then node, correct it first,
|
85
|
+
# then the next pass will use `correct_to_elsif_from_if_inside_else_form`
|
86
|
+
IfThenCorrector.new(node, indentation: 0).call(corrector)
|
87
|
+
return
|
88
|
+
end
|
89
|
+
|
83
90
|
if node.modifier_form?
|
84
91
|
correct_to_elsif_from_modifier_form(corrector, node)
|
85
92
|
else
|
86
93
|
correct_to_elsif_from_if_inside_else_form(corrector, node, node.condition)
|
87
94
|
end
|
95
|
+
|
88
96
|
corrector.remove(range_by_whole_lines(find_end_range(node), include_final_newline: true))
|
89
97
|
return unless (if_branch = node.if_branch)
|
90
98
|
|
@@ -102,15 +110,22 @@ module RuboCop
|
|
102
110
|
|
103
111
|
def correct_to_elsif_from_if_inside_else_form(corrector, node, condition)
|
104
112
|
corrector.replace(node.parent.loc.else, "elsif #{condition.source}")
|
113
|
+
|
105
114
|
if_condition_range = if_condition_range(node, condition)
|
115
|
+
|
106
116
|
if (if_branch = node.if_branch)
|
107
117
|
corrector.replace(if_condition_range, if_branch.source)
|
108
118
|
else
|
109
119
|
corrector.remove(range_by_whole_lines(if_condition_range, include_final_newline: true))
|
110
120
|
end
|
121
|
+
|
111
122
|
corrector.remove(condition)
|
112
123
|
end
|
113
124
|
|
125
|
+
def then?(node)
|
126
|
+
node.loc.begin&.source == 'then'
|
127
|
+
end
|
128
|
+
|
114
129
|
def find_end_range(node)
|
115
130
|
end_range = node.loc.end
|
116
131
|
return end_range if end_range
|
@@ -22,45 +22,37 @@ module RuboCop
|
|
22
22
|
include ConfigurableEnforcedStyle
|
23
23
|
extend AutoCorrector
|
24
24
|
|
25
|
+
MSG = 'Prefer the use of `%<prefer>s` over `%<current>s`.'
|
25
26
|
RESTRICT_ON_SEND = %i[call].freeze
|
26
27
|
|
27
28
|
def on_send(node)
|
28
29
|
return unless node.receiver
|
29
30
|
|
30
31
|
if offense?(node)
|
31
|
-
|
32
|
+
prefer = prefer(node)
|
33
|
+
current = node.source
|
34
|
+
|
35
|
+
add_offense(node, message: format(MSG, prefer: prefer, current: current)) do |corrector|
|
32
36
|
opposite_style_detected
|
33
|
-
|
37
|
+
corrector.replace(node, prefer)
|
34
38
|
end
|
35
39
|
else
|
36
40
|
correct_style_detected
|
37
41
|
end
|
38
42
|
end
|
39
43
|
|
40
|
-
def autocorrect(corrector, node)
|
41
|
-
if explicit_style?
|
42
|
-
receiver = node.receiver.source
|
43
|
-
replacement = node.source.sub("#{receiver}.", "#{receiver}.call")
|
44
|
-
|
45
|
-
corrector.replace(node, replacement)
|
46
|
-
else
|
47
|
-
add_parentheses(node, corrector) unless node.parenthesized?
|
48
|
-
corrector.remove(node.loc.selector)
|
49
|
-
end
|
50
|
-
end
|
51
|
-
|
52
44
|
private
|
53
45
|
|
54
46
|
def offense?(node)
|
55
47
|
(explicit_style? && node.implicit_call?) || (implicit_style? && !node.implicit_call?)
|
56
48
|
end
|
57
49
|
|
58
|
-
def
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
50
|
+
def prefer(node)
|
51
|
+
receiver = node.receiver.source
|
52
|
+
arguments = node.arguments.map(&:source).join(', ')
|
53
|
+
method = explicit_style? ? "call(#{arguments})" : "(#{arguments})"
|
54
|
+
|
55
|
+
"#{receiver}.#{method}"
|
64
56
|
end
|
65
57
|
|
66
58
|
def implicit_style?
|
@@ -0,0 +1,68 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module Style
|
6
|
+
# This cop looks for uses of `map.to_h` or `collect.to_h` that could be
|
7
|
+
# written with just `to_h` in Ruby >= 2.6.
|
8
|
+
#
|
9
|
+
# NOTE: `Style/HashTransformKeys` and `Style/HashTransformValues` will
|
10
|
+
# also change this pattern if only hash keys or hash values are being
|
11
|
+
# transformed.
|
12
|
+
#
|
13
|
+
# @safety
|
14
|
+
# This cop is unsafe, as it can produce false positives if the receiver
|
15
|
+
# is not an `Enumerable`.
|
16
|
+
#
|
17
|
+
# @example
|
18
|
+
# # bad
|
19
|
+
# something.map { |v| [v, v * 2] }.to_h
|
20
|
+
#
|
21
|
+
# # good
|
22
|
+
# something.to_h { |v| [v, v * 2] }
|
23
|
+
#
|
24
|
+
# # bad
|
25
|
+
# {foo: bar}.collect { |k, v| [k.to_s, v.do_something] }.to_h
|
26
|
+
#
|
27
|
+
# # good
|
28
|
+
# {foo: bar}.to_h { |k, v| [k.to_s, v.do_something] }
|
29
|
+
#
|
30
|
+
class MapToHash < Base
|
31
|
+
extend AutoCorrector
|
32
|
+
extend TargetRubyVersion
|
33
|
+
include RangeHelp
|
34
|
+
|
35
|
+
minimum_target_ruby_version 2.6
|
36
|
+
|
37
|
+
MSG = 'Pass a block to `to_h` instead of calling `%<method>s.to_h`.'
|
38
|
+
RESTRICT_ON_SEND = %i[to_h].freeze
|
39
|
+
|
40
|
+
# @!method map_to_h?(node)
|
41
|
+
def_node_matcher :map_to_h?, <<~PATTERN
|
42
|
+
$(send (block $(send _ {:map :collect}) ...) :to_h)
|
43
|
+
PATTERN
|
44
|
+
|
45
|
+
def on_send(node)
|
46
|
+
return unless (to_h_node, map_node = map_to_h?(node))
|
47
|
+
|
48
|
+
message = format(MSG, method: map_node.loc.selector.source)
|
49
|
+
add_offense(map_node.loc.selector, message: message) do |corrector|
|
50
|
+
# If the `to_h` call already has a block, do not auto-correct.
|
51
|
+
next if to_h_node.block_node
|
52
|
+
|
53
|
+
autocorrect(corrector, to_h_node, map_node)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
private
|
58
|
+
|
59
|
+
def autocorrect(corrector, to_h, map)
|
60
|
+
removal_range = range_between(to_h.loc.dot.begin_pos, to_h.loc.selector.end_pos)
|
61
|
+
|
62
|
+
corrector.remove(range_with_surrounding_space(range: removal_range, side: :left))
|
63
|
+
corrector.replace(map.loc.selector, 'to_h')
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -13,9 +13,11 @@ module RuboCop
|
|
13
13
|
|
14
14
|
private
|
15
15
|
|
16
|
+
# rubocop:disable Metrics/PerceivedComplexity
|
16
17
|
def omit_parentheses(node)
|
17
18
|
return unless node.parenthesized?
|
18
19
|
return if inside_endless_method_def?(node)
|
20
|
+
return if require_parentheses_for_hash_value_omission?(node)
|
19
21
|
return if syntax_like_method_call?(node)
|
20
22
|
return if super_call_without_arguments?(node)
|
21
23
|
return if allowed_camel_case_method_call?(node)
|
@@ -26,6 +28,7 @@ module RuboCop
|
|
26
28
|
auto_correct(corrector, node)
|
27
29
|
end
|
28
30
|
end
|
31
|
+
# rubocop:enable Metrics/PerceivedComplexity
|
29
32
|
|
30
33
|
def auto_correct(corrector, node)
|
31
34
|
if parentheses_at_the_end_of_multiline_call?(node)
|
@@ -45,6 +48,23 @@ module RuboCop
|
|
45
48
|
node.each_ancestor(:def, :defs).any?(&:endless?) && node.arguments.any?
|
46
49
|
end
|
47
50
|
|
51
|
+
def require_parentheses_for_hash_value_omission?(node)
|
52
|
+
return false unless (last_argument = node.last_argument)
|
53
|
+
return false if !last_argument.hash_type? || !last_argument.pairs.last&.value_omission?
|
54
|
+
|
55
|
+
modifier_form?(node) || exist_next_line_expression?(node)
|
56
|
+
end
|
57
|
+
|
58
|
+
def modifier_form?(node)
|
59
|
+
node.parent.respond_to?(:modifier_form?) && node.parent.modifier_form?
|
60
|
+
end
|
61
|
+
|
62
|
+
# Require hash value omission be enclosed in parentheses to prevent the following issue:
|
63
|
+
# https://bugs.ruby-lang.org/issues/18396.
|
64
|
+
def exist_next_line_expression?(node)
|
65
|
+
node.parent&.assignment? ? node.parent.right_sibling : node.right_sibling
|
66
|
+
end
|
67
|
+
|
48
68
|
def syntax_like_method_call?(node)
|
49
69
|
node.implicit_call? || node.operator_method?
|
50
70
|
end
|
@@ -43,9 +43,11 @@ module RuboCop
|
|
43
43
|
# NOTE: Parentheses are still allowed in cases where omitting them
|
44
44
|
# results in ambiguous or syntactically incorrect code. For example,
|
45
45
|
# parentheses are required around a method with arguments when inside an
|
46
|
-
# endless method definition introduced in Ruby 3.0.
|
46
|
+
# endless method definition introduced in Ruby 3.0. Parentheses are also
|
47
47
|
# allowed when forwarding arguments with the triple-dot syntax introduced
|
48
48
|
# in Ruby 2.7 as omitting them starts an endless range.
|
49
|
+
# And Ruby 3.1's hash omission syntax has a case that requires parentheses
|
50
|
+
# because of the following issue: https://bugs.ruby-lang.org/issues/18396.
|
49
51
|
#
|
50
52
|
# @example EnforcedStyle: require_parentheses (default)
|
51
53
|
#
|