/
map_to_hash.rb
81 lines (70 loc) · 2.49 KB
/
map_to_hash.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
# frozen_string_literal: true
module RuboCop
module Cop
module Style
# Looks for uses of `map.to_h` or `collect.to_h` that could be
# written with just `to_h` in Ruby >= 2.6.
#
# NOTE: `Style/HashTransformKeys` and `Style/HashTransformValues` will
# also change this pattern if only hash keys or hash values are being
# transformed.
#
# @safety
# This cop is unsafe, as it can produce false positives if the receiver
# is not an `Enumerable`.
#
# @example
# # bad
# something.map { |v| [v, v * 2] }.to_h
#
# # good
# something.to_h { |v| [v, v * 2] }
#
# # bad
# {foo: bar}.collect { |k, v| [k.to_s, v.do_something] }.to_h
#
# # good
# {foo: bar}.to_h { |k, v| [k.to_s, v.do_something] }
#
class MapToHash < Base
extend AutoCorrector
extend TargetRubyVersion
include RangeHelp
minimum_target_ruby_version 2.6
MSG = 'Pass a block to `to_h` instead of calling `%<method>s%<dot>sto_h`.'
RESTRICT_ON_SEND = %i[to_h].freeze
# @!method map_to_h(node)
def_node_matcher :map_to_h, <<~PATTERN
{
$(call ({block numblock} $(call _ {:map :collect}) ...) :to_h)
$(call $(call _ {:map :collect} (block_pass sym)) :to_h)
}
PATTERN
def self.autocorrect_incompatible_with
[Layout::SingleLineBlockChain]
end
def on_send(node)
return unless (to_h_node, map_node = map_to_h(node))
message = format(MSG, method: map_node.loc.selector.source, dot: to_h_node.loc.dot.source)
add_offense(map_node.loc.selector, message: message) do |corrector|
# If the `to_h` call already has a block, do not autocorrect.
next if to_h_node.block_literal?
autocorrect(corrector, to_h_node, map_node)
end
end
alias on_csend on_send
private
# rubocop:disable Metrics/AbcSize
def autocorrect(corrector, to_h, map)
removal_range = range_between(to_h.loc.dot.begin_pos, to_h.loc.selector.end_pos)
corrector.remove(range_with_surrounding_space(removal_range, side: :left))
if (map_dot = map.loc.dot)
corrector.replace(map_dot, to_h.loc.dot.source)
end
corrector.replace(map.loc.selector, 'to_h')
end
# rubocop:enable Metrics/AbcSize
end
end
end
end