rubocop 1.22.3 → 1.25.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (99) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE.txt +1 -1
  3. data/README.md +2 -3
  4. data/config/default.yml +86 -5
  5. data/lib/rubocop/cli/command/auto_genenerate_config.rb +1 -1
  6. data/lib/rubocop/cli/command/init_dotfile.rb +1 -1
  7. data/lib/rubocop/cli/command/show_docs_url.rb +48 -0
  8. data/lib/rubocop/cli/command/suggest_extensions.rb +1 -1
  9. data/lib/rubocop/cli.rb +1 -0
  10. data/lib/rubocop/config_loader_resolver.rb +1 -1
  11. data/lib/rubocop/cop/bundler/duplicated_gem.rb +1 -1
  12. data/lib/rubocop/cop/correctors/each_to_for_corrector.rb +1 -1
  13. data/lib/rubocop/cop/correctors/if_then_corrector.rb +55 -0
  14. data/lib/rubocop/cop/documentation.rb +19 -2
  15. data/lib/rubocop/cop/gemspec/date_assignment.rb +2 -10
  16. data/lib/rubocop/cop/gemspec/duplicated_assignment.rb +1 -10
  17. data/lib/rubocop/cop/gemspec/require_mfa.rb +144 -0
  18. data/lib/rubocop/cop/gemspec/required_ruby_version.rb +10 -3
  19. data/lib/rubocop/cop/gemspec/ruby_version_globals_usage.rb +3 -10
  20. data/lib/rubocop/cop/generator.rb +5 -4
  21. data/lib/rubocop/cop/internal_affairs/redundant_method_dispatch_node.rb +47 -0
  22. data/lib/rubocop/cop/internal_affairs/undefined_config.rb +3 -1
  23. data/lib/rubocop/cop/internal_affairs.rb +1 -0
  24. data/lib/rubocop/cop/layout/argument_alignment.rb +36 -9
  25. data/lib/rubocop/cop/layout/comment_indentation.rb +31 -2
  26. data/lib/rubocop/cop/layout/dot_position.rb +4 -0
  27. data/lib/rubocop/cop/layout/empty_line_between_defs.rb +22 -1
  28. data/lib/rubocop/cop/layout/hash_alignment.rb +1 -1
  29. data/lib/rubocop/cop/layout/space_after_colon.rb +1 -1
  30. data/lib/rubocop/cop/layout/space_before_first_arg.rb +4 -0
  31. data/lib/rubocop/cop/layout/space_in_lambda_literal.rb +11 -5
  32. data/lib/rubocop/cop/lint/ambiguous_range.rb +2 -2
  33. data/lib/rubocop/cop/lint/ambiguous_regexp_literal.rb +5 -1
  34. data/lib/rubocop/cop/lint/constant_definition_in_block.rb +1 -1
  35. data/lib/rubocop/cop/lint/deprecated_class_methods.rb +16 -4
  36. data/lib/rubocop/cop/lint/deprecated_open_ssl_constant.rb +6 -0
  37. data/lib/rubocop/cop/lint/each_with_object_argument.rb +1 -1
  38. data/lib/rubocop/cop/lint/incompatible_io_select_with_fiber_scheduler.rb +10 -5
  39. data/lib/rubocop/cop/lint/number_conversion.rb +5 -2
  40. data/lib/rubocop/cop/lint/parentheses_as_grouped_expression.rb +7 -4
  41. data/lib/rubocop/cop/lint/useless_ruby2_keywords.rb +117 -0
  42. data/lib/rubocop/cop/metrics/block_length.rb +1 -0
  43. data/lib/rubocop/cop/metrics/cyclomatic_complexity.rb +0 -9
  44. data/lib/rubocop/cop/metrics/method_length.rb +1 -0
  45. data/lib/rubocop/cop/metrics/module_length.rb +1 -1
  46. data/lib/rubocop/cop/metrics/parameter_lists.rb +1 -1
  47. data/lib/rubocop/cop/metrics/utils/code_length_calculator.rb +1 -1
  48. data/lib/rubocop/cop/metrics/utils/repeated_attribute_discount.rb +1 -1
  49. data/lib/rubocop/cop/mixin/enforce_superclass.rb +5 -0
  50. data/lib/rubocop/cop/mixin/gemspec_help.rb +30 -0
  51. data/lib/rubocop/cop/mixin/hash_alignment_styles.rb +4 -3
  52. data/lib/rubocop/cop/mixin/hash_shorthand_syntax.rb +68 -0
  53. data/lib/rubocop/cop/mixin/string_literals_help.rb +1 -5
  54. data/lib/rubocop/cop/naming/block_forwarding.rb +121 -0
  55. data/lib/rubocop/cop/naming/file_name.rb +37 -4
  56. data/lib/rubocop/cop/security/open.rb +11 -1
  57. data/lib/rubocop/cop/style/character_literal.rb +8 -1
  58. data/lib/rubocop/cop/style/collection_compact.rb +31 -13
  59. data/lib/rubocop/cop/style/combinable_loops.rb +2 -2
  60. data/lib/rubocop/cop/style/documentation.rb +1 -1
  61. data/lib/rubocop/cop/style/empty_case_condition.rb +10 -0
  62. data/lib/rubocop/cop/style/empty_method.rb +1 -1
  63. data/lib/rubocop/cop/style/file_read.rb +112 -0
  64. data/lib/rubocop/cop/style/file_write.rb +124 -0
  65. data/lib/rubocop/cop/style/format_string_token.rb +2 -1
  66. data/lib/rubocop/cop/style/hash_conversion.rb +2 -1
  67. data/lib/rubocop/cop/style/hash_syntax.rb +36 -0
  68. data/lib/rubocop/cop/style/hash_transform_keys.rb +6 -6
  69. data/lib/rubocop/cop/style/hash_transform_values.rb +6 -6
  70. data/lib/rubocop/cop/style/if_inside_else.rb +15 -0
  71. data/lib/rubocop/cop/style/if_with_boolean_literal_branches.rb +1 -1
  72. data/lib/rubocop/cop/style/map_to_hash.rb +68 -0
  73. data/lib/rubocop/cop/style/method_call_with_args_parentheses/omit_parentheses.rb +14 -0
  74. data/lib/rubocop/cop/style/method_call_with_args_parentheses.rb +3 -1
  75. data/lib/rubocop/cop/style/method_def_parentheses.rb +17 -13
  76. data/lib/rubocop/cop/style/numeric_literals.rb +10 -1
  77. data/lib/rubocop/cop/style/one_line_conditional.rb +18 -39
  78. data/lib/rubocop/cop/style/open_struct_use.rb +69 -0
  79. data/lib/rubocop/cop/style/parentheses_around_condition.rb +12 -2
  80. data/lib/rubocop/cop/style/quoted_symbols.rb +11 -1
  81. data/lib/rubocop/cop/style/redundant_interpolation.rb +17 -3
  82. data/lib/rubocop/cop/style/redundant_regexp_character_class.rb +5 -1
  83. data/lib/rubocop/cop/style/redundant_self.rb +1 -1
  84. data/lib/rubocop/cop/style/safe_navigation.rb +1 -5
  85. data/lib/rubocop/cop/style/sample.rb +5 -3
  86. data/lib/rubocop/cop/style/single_line_block_params.rb +2 -2
  87. data/lib/rubocop/cop/style/sole_nested_conditional.rb +3 -1
  88. data/lib/rubocop/cop/style/ternary_parentheses.rb +16 -2
  89. data/lib/rubocop/cop/team.rb +1 -1
  90. data/lib/rubocop/cop/util.rb +9 -1
  91. data/lib/rubocop/formatter/html_formatter.rb +5 -2
  92. data/lib/rubocop/formatter/json_formatter.rb +4 -1
  93. data/lib/rubocop/options.rb +6 -1
  94. data/lib/rubocop/remote_config.rb +2 -4
  95. data/lib/rubocop/result_cache.rb +1 -1
  96. data/lib/rubocop/version.rb +1 -1
  97. data/lib/rubocop/yaml_duplication_checker.rb +1 -1
  98. data/lib/rubocop.rb +11 -0
  99. metadata +20 -7
@@ -16,6 +16,7 @@ module RuboCop
16
16
  #
17
17
  # @example
18
18
  # # bad
19
+ # array.reject(&:nil?)
19
20
  # array.reject { |e| e.nil? }
20
21
  # array.select { |e| !e.nil? }
21
22
  #
@@ -23,6 +24,7 @@ module RuboCop
23
24
  # array.compact
24
25
  #
25
26
  # # bad
27
+ # hash.reject!(&:nil?)
26
28
  # hash.reject! { |k, v| v.nil? }
27
29
  # hash.select! { |k, v| !v.nil? }
28
30
  #
@@ -37,11 +39,18 @@ module RuboCop
37
39
 
38
40
  RESTRICT_ON_SEND = %i[reject reject! select select!].freeze
39
41
 
42
+ # @!method reject_method_with_block_pass?(node)
43
+ def_node_matcher :reject_method_with_block_pass?, <<~PATTERN
44
+ (send !nil? {:reject :reject!}
45
+ (block_pass
46
+ (sym :nil?)))
47
+ PATTERN
48
+
40
49
  # @!method reject_method?(node)
41
50
  def_node_matcher :reject_method?, <<~PATTERN
42
51
  (block
43
52
  (send
44
- _ ${:reject :reject!})
53
+ !nil? {:reject :reject!})
45
54
  $(args ...)
46
55
  (send
47
56
  $(lvar _) :nil?))
@@ -51,7 +60,7 @@ module RuboCop
51
60
  def_node_matcher :select_method?, <<~PATTERN
52
61
  (block
53
62
  (send
54
- _ ${:select :select!})
63
+ !nil? {:select :select!})
55
64
  $(args ...)
56
65
  (send
57
66
  (send
@@ -59,16 +68,9 @@ module RuboCop
59
68
  PATTERN
60
69
 
61
70
  def on_send(node)
62
- block_node = node.parent
63
- return unless block_node&.block_type?
64
-
65
- return unless (method_name, args, receiver =
66
- reject_method?(block_node) || select_method?(block_node))
71
+ return unless (range = offense_range(node))
67
72
 
68
- return unless args.last.source == receiver.source
69
-
70
- range = offense_range(node, block_node)
71
- good = good_method_name(method_name)
73
+ good = good_method_name(node.method_name)
72
74
  message = format(MSG, good: good, bad: range.source)
73
75
 
74
76
  add_offense(range, message: message) { |corrector| corrector.replace(range, good) }
@@ -76,6 +78,22 @@ module RuboCop
76
78
 
77
79
  private
78
80
 
81
+ def offense_range(node)
82
+ if reject_method_with_block_pass?(node)
83
+ range(node, node)
84
+ else
85
+ block_node = node.parent
86
+
87
+ return unless block_node&.block_type?
88
+ unless (args, receiver = reject_method?(block_node) || select_method?(block_node))
89
+ return
90
+ end
91
+ return unless args.last.source == receiver.source
92
+
93
+ range(node, block_node)
94
+ end
95
+ end
96
+
79
97
  def good_method_name(method_name)
80
98
  if method_name.to_s.end_with?('!')
81
99
  'compact!'
@@ -84,8 +102,8 @@ module RuboCop
84
102
  end
85
103
  end
86
104
 
87
- def offense_range(send_node, block_node)
88
- range_between(send_node.loc.selector.begin_pos, block_node.loc.end.end_pos)
105
+ def range(begin_pos_node, end_pos_node)
106
+ range_between(begin_pos_node.loc.selector.begin_pos, end_pos_node.loc.end.end_pos)
89
107
  end
90
108
  end
91
109
  end
@@ -77,14 +77,14 @@ module RuboCop
77
77
 
78
78
  def collection_looping_method?(node)
79
79
  # TODO: Remove `Symbol#to_s` after supporting only Ruby >= 2.7.
80
- method_name = node.send_node.method_name.to_s
80
+ method_name = node.method_name.to_s
81
81
  method_name.start_with?('each') || method_name.end_with?('_each')
82
82
  end
83
83
 
84
84
  def same_collection_looping?(node, sibling)
85
85
  sibling&.block_type? &&
86
86
  sibling.send_node.method?(node.method_name) &&
87
- sibling.send_node.receiver == node.send_node.receiver &&
87
+ sibling.receiver == node.receiver &&
88
88
  sibling.send_node.arguments == node.send_node.arguments
89
89
  end
90
90
  end
@@ -176,7 +176,7 @@ module RuboCop
176
176
  end
177
177
 
178
178
  def qualify_const(node)
179
- return if node.nil?
179
+ return if node.nil? || node.cbase_type?
180
180
 
181
181
  [qualify_const(node.namespace), node.short_name].compact
182
182
  end
@@ -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
@@ -9,7 +9,7 @@ module RuboCop
9
9
  # to go on its own line (expanded style).
10
10
  #
11
11
  # NOTE: A method definition is not considered empty if it contains
12
- # comments.
12
+ # comments.
13
13
  #
14
14
  # @example EnforcedStyle: compact (default)
15
15
  # # bad
@@ -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
- (parent = node.parent) && parent.send_type? && ignored_method?(parent.method_name)
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
- ({send csend} !#array_receiver? :each_with_object (hash))
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
- ({send csend} (lvar _memo) :[]= $!`_memo $(lvar _val)))
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
- ({send csend} !#array_receiver? {:map :collect})
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
- ({send csend}
57
+ (call
58
58
  (block
59
- ({send csend} !#array_receiver? {:map :collect})
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
- ({send csend} !#array_receiver? :to_h)
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
- ({send csend} !#array_receiver? :each_with_object (hash))
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
- ({send csend} (lvar _memo) :[]= $(lvar _key) $!`_memo))
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
- ({send csend} !#array_receiver? {:map :collect})
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
- ({send csend}
57
+ (call
58
58
  (block
59
- ({send csend} !#array_receiver? {:map :collect})
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
- ({send csend} !#array_receiver? :to_h)
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
@@ -36,7 +36,7 @@ module RuboCop
36
36
  # # good (but potentially an unsafe correction)
37
37
  # foo.do_something?
38
38
  #
39
- # @example AllowedMethods: ['nonzero?']
39
+ # @example AllowedMethods: ['nonzero?'] (default)
40
40
  # # good
41
41
  # num.nonzero? ? true : false
42
42
  #
@@ -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