forked from rubocop/rubocop-rspec
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathmultiple_expectations.rb
143 lines (123 loc) · 4.17 KB
/
multiple_expectations.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
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
# frozen_string_literal: true
module RuboCop
module Cop
module RSpec
# Checks if examples contain too many `expect` calls.
#
# @see http://betterspecs.org/#single Single expectation test
#
# This cop is configurable using the `Max` option
# and works with `--auto-gen-config`.
#
# @example
# # bad
# describe UserCreator do
# it 'builds a user' do
# expect(user.name).to eq("John")
# expect(user.age).to eq(22)
# end
# end
#
# # good
# describe UserCreator do
# it 'sets the users name' do
# expect(user.name).to eq("John")
# end
#
# it 'sets the users age' do
# expect(user.age).to eq(22)
# end
# end
#
# @example `aggregate_failures: true` (default)
# # good - the cop ignores when RSpec aggregates failures
# describe UserCreator do
# it 'builds a user', :aggregate_failures do
# expect(user.name).to eq("John")
# expect(user.age).to eq(22)
# end
# end
#
# @example `aggregate_failures: false`
# # Detected as an offense
# describe UserCreator do
# it 'builds a user', aggregate_failures: false do
# expect(user.name).to eq("John")
# expect(user.age).to eq(22)
# end
# end
#
# @example configuration
# # .rubocop.yml
# # RSpec/MultipleExpectations:
# # Max: 2
#
# # not flagged by rubocop
# describe UserCreator do
# it 'builds a user' do
# expect(user.name).to eq("John")
# expect(user.age).to eq(22)
# end
# end
#
class MultipleExpectations < Base
include ConfigurableMax
MSG = 'Example has too many expectations [%<total>d/%<max>d].'
ANYTHING = ->(_node) { true }
TRUE = ->(node) { node.true_type? }
# @!method aggregate_failures?(node)
def_node_matcher :aggregate_failures?, <<-PATTERN
(block {
(send _ _ <(sym :aggregate_failures) ...>)
(send _ _ ... (hash <(pair (sym :aggregate_failures) %1) ...>))
} ...)
PATTERN
# @!method expect?(node)
def_node_matcher :expect?, send_pattern('#Expectations.all')
# @!method aggregate_failures_block?(node)
def_node_matcher :aggregate_failures_block?, <<-PATTERN
(block (send nil? :aggregate_failures ...) ...)
PATTERN
def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler
return unless example?(node)
return if example_with_aggregate_failures?(node)
expectations_count = to_enum(:find_expectation, node).count
return if expectations_count <= max_expectations
self.max = expectations_count
flag_example(node, expectation_count: expectations_count)
end
private
def example_with_aggregate_failures?(example_node)
node_with_aggregate_failures = find_aggregate_failures(example_node)
return false unless node_with_aggregate_failures
aggregate_failures?(node_with_aggregate_failures, TRUE)
end
def find_aggregate_failures(example_node)
example_node.send_node.each_ancestor(:block)
.find { |block_node| aggregate_failures?(block_node, ANYTHING) }
end
def find_expectation(node, &block)
yield if expect?(node) || aggregate_failures_block?(node)
# do not search inside of aggregate_failures block
return if aggregate_failures_block?(node)
node.each_child_node do |child|
find_expectation(child, &block)
end
end
def flag_example(node, expectation_count:)
add_offense(
node.send_node,
message: format(
MSG,
total: expectation_count,
max: max_expectations
)
)
end
def max_expectations
Integer(cop_config.fetch('Max', 1))
end
end
end
end
end