-
-
Notifications
You must be signed in to change notification settings - Fork 3k
/
compound_hash.rb
106 lines (94 loc) · 3.29 KB
/
compound_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
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
# frozen_string_literal: true
module RuboCop
module Cop
module Security
# Checks for implementations of the `hash` method which combine
# values using custom logic instead of delegating to `Array#hash`.
#
# Manually combining hashes is error prone and hard to follow, especially
# when there are many values. Poor implementations may also introduce
# performance or security concerns if they are prone to collisions.
# Delegating to `Array#hash` is clearer and safer, although it might be slower
# depending on the use case.
#
# @safety
# This cop may be unsafe if the application logic depends on the hash
# value, however this is inadvisable anyway.
#
# @example
#
# # bad
# def hash
# @foo ^ @bar
# end
#
# # good
# def hash
# [@foo, @bar].hash
# end
class CompoundHash < Base
COMBINATOR_IN_HASH_MSG = 'Use `[...].hash` instead of combining hash values manually.'
MONUPLE_HASH_MSG =
'Delegate hash directly without wrapping in an array when only using a single value.'
REDUNDANT_HASH_MSG = 'Calling .hash on elements of a hashed array is redundant.'
# @!method hash_method_definition?(node)
def_node_matcher :hash_method_definition?, <<~PATTERN
{#static_hash_method_definition? | #dynamic_hash_method_definition?}
PATTERN
# @!method dynamic_hash_method_definition?(node)
def_node_matcher :dynamic_hash_method_definition?, <<~PATTERN
(block
(send _ {:define_method | :define_singleton_method}
(sym :hash))
(args)
_)
PATTERN
# @!method static_hash_method_definition?(node)
def_node_matcher :static_hash_method_definition?, <<~PATTERN
({def | defs _} :hash
(args)
_)
PATTERN
# @!method bad_hash_combinator?(node)
def_node_matcher :bad_hash_combinator?, <<~PATTERN
({send | op-asgn} _ {:^ | :+ | :* | :|} _)
PATTERN
# @!method monuple_hash?(node)
def_node_matcher :monuple_hash?, <<~PATTERN
(send (array _) :hash)
PATTERN
# @!method redundant_hash?(node)
def_node_matcher :redundant_hash?, <<~PATTERN
(
^^(send array ... :hash)
_ :hash
)
PATTERN
def contained_in_hash_method?(node, &block)
node.each_ancestor.any? do |ancestor|
hash_method_definition?(ancestor, &block)
end
end
def outer_bad_hash_combinator?(node)
bad_hash_combinator?(node) do
yield true if node.each_ancestor.none? { |ancestor| bad_hash_combinator?(ancestor) }
end
end
def on_send(node)
outer_bad_hash_combinator?(node) do
contained_in_hash_method?(node) do
add_offense(node, message: COMBINATOR_IN_HASH_MSG)
end
end
monuple_hash?(node) do
add_offense(node, message: MONUPLE_HASH_MSG)
end
redundant_hash?(node) do
add_offense(node, message: REDUNDANT_HASH_MSG)
end
end
alias on_op_asgn on_send
end
end
end
end