-
-
Notifications
You must be signed in to change notification settings - Fork 1
/
hash_element_ordered.rb
127 lines (113 loc) · 3.03 KB
/
hash_element_ordered.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
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
# frozen_string_literal: true
module RuboCop
module Cop
module Sevencop
# Sort Hash elements by key.
#
# @example
#
# # bad
# {
# b: 1,
# a: 1,
# c: 1
# }
#
# # good
# {
# a: 1,
# b: 1,
# c: 1
# }
#
class HashElementOrdered < Base
extend AutoCorrector
MSG = 'Sort Hash elements by key.'
# @!method hash_literal?(node)
def_node_matcher :hash_literal?, <<~PATTERN
(hash
(pair
{sym | str}
_
)+
)
PATTERN
# @param [RuboCop::AST::HashNode] node
def on_hash(node)
return unless hash_literal?(node)
return if sorted?(node)
add_offense(node) do |corrector|
corrector.replace(
node,
autocorrect(node)
)
end
end
private
# @param [RuboCop::AST::HashNode] node
# @return [String]
def autocorrect(node)
parts = [
whitespace_leading(node),
sort(node.pairs).map(&:source).join(",#{whitespace_between(node)}"),
whitespace_trailing(node)
]
parts = ['{', *parts, '}'] if node.braces?
parts.join
end
# @param [RuboCop::AST::HashNode] node
# @return [Integer]
def offset_for(node)
if node.braces?
1
else
0
end
end
# @param [Array<RuboCop::AST::PairNode>] pairs
# @return [Array<RuboCop::AST::PairNode>]
def sort(pairs)
pairs.sort_by do |pair|
pair.key.value
end
end
# @param [RuboCop::AST::HashNode] node
# @return [Boolean]
def sorted?(node)
node.pairs.map(&:source) == sort(node.pairs).map(&:source)
end
# @param [RuboCop::AST::HashNode] node
# @return [String]
# { a: 1, b: 1 }
# ^^^
def whitespace_between(node)
if node.pairs.length >= 2
processed_source.raw_source[
node.pairs[0].source_range.end_pos + 1...node.pairs[1].source_range.begin_pos
]
else
' '
end
end
# @param [RuboCop::AST::HashNode] node
# @return [String]
# { a: 1, b: 1 }
# ^^^^
def whitespace_leading(node)
processed_source.raw_source[
node.source_range.begin.end_pos + offset_for(node)...node.pairs[0].source_range.begin_pos
]
end
# @param [RuboCop::AST::HashNode] node
# @return [String]
# { a: 1, b: 1 }
# ^^
def whitespace_trailing(node)
processed_source.raw_source[
node.pairs[-1].source_range.end_pos...node.source_range.end.begin_pos - offset_for(node)
]
end
end
end
end
end