rubocop-rspec 1.5.2 → 1.5.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +4 -0
- data/config/default.yml +4 -0
- data/lib/rubocop-rspec.rb +1 -0
- data/lib/rubocop/cop/rspec/named_subject.rb +67 -0
- data/lib/rubocop/rspec/version.rb +1 -1
- data/rubocop-rspec.gemspec +3 -0
- data/spec/expect_violation/expectation_spec.rb +85 -0
- data/spec/rubocop/cop/rspec/any_instance_spec.rb +18 -36
- data/spec/rubocop/cop/rspec/describe_class_spec.rb +65 -62
- data/spec/rubocop/cop/rspec/describe_method_spec.rb +17 -18
- data/spec/rubocop/cop/rspec/described_class_spec.rb +89 -139
- data/spec/rubocop/cop/rspec/example_wording_spec.rb +20 -29
- data/spec/rubocop/cop/rspec/focus_spec.rb +115 -63
- data/spec/rubocop/cop/rspec/instance_variable_spec.rb +23 -35
- data/spec/rubocop/cop/rspec/multiple_describes_spec.rb +16 -31
- data/spec/rubocop/cop/rspec/named_subject_spec.rb +62 -0
- data/spec/rubocop/cop/rspec/not_to_not_spec.rb +14 -16
- data/spec/rubocop/cop/rspec/verified_doubles_spec.rb +39 -43
- data/spec/spec_helper.rb +5 -0
- data/spec/support/expect_violation.rb +166 -0
- metadata +51 -2
@@ -2,49 +2,37 @@ describe RuboCop::Cop::RSpec::InstanceVariable do
|
|
2
2
|
subject(:cop) { described_class.new }
|
3
3
|
|
4
4
|
it 'finds an instance variable inside a describe' do
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
]
|
13
|
-
)
|
14
|
-
expect(cop.offenses.size).to eq(1)
|
15
|
-
expect(cop.offenses.map(&:line).sort).to eq([3])
|
16
|
-
expect(cop.messages).to eq(['Use `let` instead of an instance variable'])
|
5
|
+
expect_violation(<<-RUBY)
|
6
|
+
describe MyClass do
|
7
|
+
before { @foo = [] }
|
8
|
+
it { expect(@foo).to be_empty }
|
9
|
+
^^^^ Use `let` instead of an instance variable
|
10
|
+
end
|
11
|
+
RUBY
|
17
12
|
end
|
18
13
|
|
19
14
|
it 'ignores non-spec blocks' do
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
'end'
|
27
|
-
]
|
28
|
-
)
|
29
|
-
expect(cop.offenses).to be_empty
|
15
|
+
expect_no_violations(<<-RUBY)
|
16
|
+
not_rspec do
|
17
|
+
before { @foo = [] }
|
18
|
+
it { expect(@foo).to be_empty }
|
19
|
+
end
|
20
|
+
RUBY
|
30
21
|
end
|
31
22
|
|
32
23
|
it 'finds an instance variable inside a shared example' do
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
]
|
40
|
-
)
|
41
|
-
expect(cop.offenses.size).to eq(1)
|
42
|
-
expect(cop.offenses.map(&:line).sort).to eq([2])
|
43
|
-
expect(cop.messages).to eq(['Use `let` instead of an instance variable'])
|
24
|
+
expect_violation(<<-RUBY)
|
25
|
+
shared_examples 'shared example' do
|
26
|
+
it { expect(@foo).to be_empty }
|
27
|
+
^^^^ Use `let` instead of an instance variable
|
28
|
+
end
|
29
|
+
RUBY
|
44
30
|
end
|
45
31
|
|
46
32
|
it 'ignores an instance variable without describe' do
|
47
|
-
|
48
|
-
|
33
|
+
expect_no_violations(<<-RUBY)
|
34
|
+
@foo = []
|
35
|
+
@foo.empty?
|
36
|
+
RUBY
|
49
37
|
end
|
50
38
|
end
|
@@ -2,42 +2,27 @@ describe RuboCop::Cop::RSpec::MultipleDescribes do
|
|
2
2
|
subject(:cop) { described_class.new }
|
3
3
|
|
4
4
|
it 'finds multiple top level describes with class and method' do
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
]
|
11
|
-
)
|
12
|
-
expect(cop.offenses.size).to eq(1)
|
13
|
-
expect(cop.offenses.map(&:line).sort).to eq([1])
|
14
|
-
expect(cop.messages).to eq(['Do not use multiple top level describes - ' \
|
15
|
-
'try to nest them.'])
|
5
|
+
expect_violation(<<-RUBY)
|
6
|
+
describe MyClass, '.do_something' do; end
|
7
|
+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Do not use multiple top level describes - try to nest them.
|
8
|
+
describe MyClass, '.do_something_else' do; end
|
9
|
+
RUBY
|
16
10
|
end
|
17
11
|
|
18
12
|
it 'finds multiple top level describes only with class' do
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
]
|
25
|
-
)
|
26
|
-
expect(cop.offenses.size).to eq(1)
|
27
|
-
expect(cop.offenses.map(&:line).sort).to eq([1])
|
28
|
-
expect(cop.messages).to eq(['Do not use multiple top level describes - ' \
|
29
|
-
'try to nest them.'])
|
13
|
+
expect_violation(<<-RUBY)
|
14
|
+
describe MyClass do; end
|
15
|
+
^^^^^^^^^^^^^^^^ Do not use multiple top level describes - try to nest them.
|
16
|
+
describe MyOtherClass do; end
|
17
|
+
RUBY
|
30
18
|
end
|
31
19
|
|
32
20
|
it 'skips single top level describe' do
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
]
|
40
|
-
)
|
41
|
-
expect(cop.offenses).to be_empty
|
21
|
+
expect_no_violations(<<-RUBY)
|
22
|
+
require 'spec_helper'
|
23
|
+
|
24
|
+
describe MyClass do
|
25
|
+
end
|
26
|
+
RUBY
|
42
27
|
end
|
43
28
|
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
RSpec.describe RuboCop::Cop::RSpec::NamedSubject do
|
4
|
+
subject(:cop) { described_class.new }
|
5
|
+
|
6
|
+
it 'checks `it` and `specify` for explicit subject usage' do
|
7
|
+
expect_violation(<<-RUBY)
|
8
|
+
RSpec.describe User do
|
9
|
+
subject { described_class.new }
|
10
|
+
|
11
|
+
it "is valid" do
|
12
|
+
expect(subject.valid?).to be(true)
|
13
|
+
^^^^^^^ Name your test subject if you need to reference it explicitly.
|
14
|
+
end
|
15
|
+
|
16
|
+
specify do
|
17
|
+
expect(subject.valid?).to be(true)
|
18
|
+
^^^^^^^ Name your test subject if you need to reference it explicitly.
|
19
|
+
end
|
20
|
+
end
|
21
|
+
RUBY
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'checks before and after for explicit subject usage' do
|
25
|
+
expect_violation(<<-RUBY)
|
26
|
+
RSpec.describe User do
|
27
|
+
subject { described_class.new }
|
28
|
+
|
29
|
+
before(:each) do
|
30
|
+
do_something_with(subject)
|
31
|
+
^^^^^^^ Name your test subject if you need to reference it explicitly.
|
32
|
+
end
|
33
|
+
|
34
|
+
after do
|
35
|
+
do_something_with(subject)
|
36
|
+
^^^^^^^ Name your test subject if you need to reference it explicitly.
|
37
|
+
end
|
38
|
+
end
|
39
|
+
RUBY
|
40
|
+
end
|
41
|
+
|
42
|
+
it 'checks around(:each) for explicit subject usage' do
|
43
|
+
expect_violation(<<-RUBY)
|
44
|
+
RSpec.describe User do
|
45
|
+
subject { described_class.new }
|
46
|
+
|
47
|
+
around(:each) do |test|
|
48
|
+
do_something_with(subject)
|
49
|
+
^^^^^^^ Name your test subject if you need to reference it explicitly.
|
50
|
+
end
|
51
|
+
end
|
52
|
+
RUBY
|
53
|
+
end
|
54
|
+
|
55
|
+
it 'ignores subject when not wrapped inside a test' do
|
56
|
+
expect_no_violations(<<-RUBY)
|
57
|
+
def foo
|
58
|
+
it(subject)
|
59
|
+
end
|
60
|
+
RUBY
|
61
|
+
end
|
62
|
+
end
|
@@ -5,17 +5,16 @@ describe RuboCop::Cop::RSpec::NotToNot, :config do
|
|
5
5
|
let(:cop_config) { { 'EnforcedStyle' => 'not_to' } }
|
6
6
|
|
7
7
|
it 'detects the `to_not` offense' do
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
expect(subject.offenses.map(&:line).sort).to eq([1])
|
8
|
+
expect_violation(<<-RUBY)
|
9
|
+
it { expect(false).to_not be_true }
|
10
|
+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Prefer `not_to` over `to_not`
|
11
|
+
RUBY
|
13
12
|
end
|
14
13
|
|
15
14
|
it 'detects no offense when using `not_to`' do
|
16
|
-
|
17
|
-
|
18
|
-
|
15
|
+
expect_no_violations(<<-RUBY)
|
16
|
+
it { expect(false).not_to be_true }
|
17
|
+
RUBY
|
19
18
|
end
|
20
19
|
|
21
20
|
it 'auto-corrects `to_not` to `not_to`' do
|
@@ -28,17 +27,16 @@ describe RuboCop::Cop::RSpec::NotToNot, :config do
|
|
28
27
|
let(:cop_config) { { 'EnforcedStyle' => 'to_not' } }
|
29
28
|
|
30
29
|
it 'detects the `not_to` offense' do
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
expect(subject.offenses.map(&:line).sort).to eq([1])
|
30
|
+
expect_violation(<<-RUBY)
|
31
|
+
it { expect(false).not_to be_true }
|
32
|
+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Prefer `to_not` over `not_to`
|
33
|
+
RUBY
|
36
34
|
end
|
37
35
|
|
38
36
|
it 'detects no offense when using `to_not`' do
|
39
|
-
|
40
|
-
|
41
|
-
|
37
|
+
expect_no_violations(<<-RUBY)
|
38
|
+
it { expect(false).to_not be_true }
|
39
|
+
RUBY
|
42
40
|
end
|
43
41
|
|
44
42
|
it 'auto-corrects `not_to` to `to_not`' do
|
@@ -2,39 +2,33 @@ describe RuboCop::Cop::RSpec::VerifiedDoubles, :config do
|
|
2
2
|
subject(:cop) { described_class.new(config) }
|
3
3
|
|
4
4
|
it 'finds a `double` instead of an `instance_double`' do
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
expect(cop.offenses.map(&:line).sort).to eq([2])
|
12
|
-
expect(cop.offenses.map(&:to_s).sort).to all(
|
13
|
-
eql('C: 2: 9: Prefer using verifying doubles over normal doubles.')
|
14
|
-
)
|
5
|
+
expect_violation(<<-RUBY)
|
6
|
+
it do
|
7
|
+
foo = double("Widget")
|
8
|
+
^^^^^^^^^^^^^^^^ Prefer using verifying doubles over normal doubles.
|
9
|
+
end
|
10
|
+
RUBY
|
15
11
|
end
|
16
12
|
|
17
13
|
context 'when configuration does not specify IgnoreSymbolicNames' do
|
18
14
|
let(:cop_config) { Hash.new }
|
19
15
|
|
20
16
|
it 'find doubles whose name is a symbol' do
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
expect(cop.offenses.map(&:line).sort).to eq([2])
|
17
|
+
expect_violation(<<-RUBY)
|
18
|
+
it do
|
19
|
+
foo = double(:widget)
|
20
|
+
^^^^^^^^^^^^^^^ Prefer using verifying doubles over normal doubles.
|
21
|
+
end
|
22
|
+
RUBY
|
28
23
|
end
|
29
24
|
|
30
25
|
it 'finds a `spy` instead of an `instance_spy`' do
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
expect(cop.offenses.map(&:line).sort).to eq([2])
|
26
|
+
expect_violation(<<-RUBY)
|
27
|
+
it do
|
28
|
+
foo = spy("Widget")
|
29
|
+
^^^^^^^^^^^^^ Prefer using verifying doubles over normal doubles.
|
30
|
+
end
|
31
|
+
RUBY
|
38
32
|
end
|
39
33
|
end
|
40
34
|
|
@@ -42,34 +36,36 @@ describe RuboCop::Cop::RSpec::VerifiedDoubles, :config do
|
|
42
36
|
let(:cop_config) { { 'IgnoreSymbolicNames' => true } }
|
43
37
|
|
44
38
|
it 'ignores doubles whose name is a symbol' do
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
39
|
+
expect_no_violations(<<-RUBY)
|
40
|
+
it do
|
41
|
+
foo = double(:widget)
|
42
|
+
end
|
43
|
+
RUBY
|
49
44
|
end
|
50
45
|
|
51
46
|
it 'still flags doubles whose name is a string' do
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
)
|
47
|
+
expect_violation(<<-RUBY)
|
48
|
+
it do
|
49
|
+
foo = double("widget")
|
50
|
+
^^^^^^^^^^^^^^^^ Prefer using verifying doubles over normal doubles.
|
51
|
+
end
|
52
|
+
RUBY
|
59
53
|
end
|
60
54
|
end
|
61
55
|
|
62
56
|
it 'ignores doubles without a name' do
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
57
|
+
expect_no_violations(<<-RUBY)
|
58
|
+
it do
|
59
|
+
foo = double
|
60
|
+
end
|
61
|
+
RUBY
|
67
62
|
end
|
68
63
|
|
69
64
|
it 'ignores instance_doubles' do
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
65
|
+
expect_no_violations(<<-RUBY)
|
66
|
+
it do
|
67
|
+
foo = instance_double("Foo")
|
68
|
+
end
|
69
|
+
RUBY
|
74
70
|
end
|
75
71
|
end
|
data/spec/spec_helper.rb
CHANGED
@@ -7,6 +7,8 @@ if ENV['CI']
|
|
7
7
|
CodeClimate::TestReporter.start
|
8
8
|
end
|
9
9
|
|
10
|
+
Dir.glob(File.expand_path('support/*.rb', __dir__)).map(&method(:require))
|
11
|
+
|
10
12
|
RSpec.configure do |config|
|
11
13
|
config.order = :random
|
12
14
|
|
@@ -17,8 +19,11 @@ RSpec.configure do |config|
|
|
17
19
|
config.mock_with :rspec do |mocks|
|
18
20
|
mocks.syntax = :expect # Disable `should_receive` and `stub`
|
19
21
|
end
|
22
|
+
|
23
|
+
config.include(ExpectViolation)
|
20
24
|
end
|
21
25
|
|
22
26
|
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
23
27
|
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
28
|
+
|
24
29
|
require 'rubocop-rspec'
|
@@ -0,0 +1,166 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'adamantium'
|
4
|
+
require 'concord'
|
5
|
+
require 'anima'
|
6
|
+
|
7
|
+
module ExpectViolation
|
8
|
+
DEFAULT_FILENAME = 'example_spec.rb'.freeze
|
9
|
+
|
10
|
+
# rubocop:disable Metrics/AbcSize
|
11
|
+
def expect_violation(source, filename: DEFAULT_FILENAME)
|
12
|
+
expectation = Expectation.new(source)
|
13
|
+
inspect_source(cop, expectation.source, filename)
|
14
|
+
offenses = cop.offenses.map(&method(:to_assertion)).sort
|
15
|
+
|
16
|
+
if expectation.assertions.empty?
|
17
|
+
raise 'Use expect_no_violations to assert no violations'
|
18
|
+
end
|
19
|
+
|
20
|
+
expect(offenses).to eq(expectation.assertions.sort)
|
21
|
+
end
|
22
|
+
|
23
|
+
def expect_no_violations(source, filename: DEFAULT_FILENAME)
|
24
|
+
inspect_source(cop, source, filename)
|
25
|
+
|
26
|
+
expect(cop.offenses.empty?).to be(true)
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def to_assertion(offense)
|
32
|
+
Expectation::Assertion.new(
|
33
|
+
message: offense.message,
|
34
|
+
line_number: offense.location.line,
|
35
|
+
column_range: offense.location.column_range
|
36
|
+
)
|
37
|
+
end
|
38
|
+
|
39
|
+
class Expectation
|
40
|
+
VIOLATION_LINE_PATTERN = /\A *\^/
|
41
|
+
|
42
|
+
VIOLATION = :violation
|
43
|
+
SOURCE = :line
|
44
|
+
|
45
|
+
include Adamantium, Concord.new(:string)
|
46
|
+
|
47
|
+
def source
|
48
|
+
source_map.to_s
|
49
|
+
end
|
50
|
+
|
51
|
+
def assertions
|
52
|
+
source_map.assertions
|
53
|
+
end
|
54
|
+
|
55
|
+
private
|
56
|
+
|
57
|
+
def source_map
|
58
|
+
tokens.reduce(Source::BLANK) do |source, (type, tokens)|
|
59
|
+
tokens.reduce(source, :"add_#{type}")
|
60
|
+
end
|
61
|
+
end
|
62
|
+
memoize :source_map
|
63
|
+
|
64
|
+
def tokens
|
65
|
+
string.each_line.chunk do |line|
|
66
|
+
next SOURCE unless line =~ VIOLATION_LINE_PATTERN
|
67
|
+
|
68
|
+
VIOLATION
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
class Source
|
73
|
+
include Concord.new(:lines)
|
74
|
+
|
75
|
+
BLANK = new([].freeze)
|
76
|
+
|
77
|
+
def add_line(line)
|
78
|
+
self.class.new(lines + [Line.new(text: line, number: lines.size + 1)])
|
79
|
+
end
|
80
|
+
|
81
|
+
def add_violation(violation)
|
82
|
+
self.class.new([*lines[0...-1], lines.last.add_violation(violation)])
|
83
|
+
end
|
84
|
+
|
85
|
+
def to_s
|
86
|
+
lines.map(&:text).join
|
87
|
+
end
|
88
|
+
|
89
|
+
def assertions
|
90
|
+
lines.flat_map(&:assertions)
|
91
|
+
end
|
92
|
+
|
93
|
+
class Line
|
94
|
+
DEFAULTS = { violations: [] }.freeze
|
95
|
+
|
96
|
+
include Anima.new(:text, :number, :violations)
|
97
|
+
|
98
|
+
def initialize(options)
|
99
|
+
super(DEFAULTS.merge(options))
|
100
|
+
end
|
101
|
+
|
102
|
+
def add_violation(violation)
|
103
|
+
with(violations: violations + [violation])
|
104
|
+
end
|
105
|
+
|
106
|
+
def assertions
|
107
|
+
violations.map do |violation|
|
108
|
+
Assertion.parse(
|
109
|
+
text: violation,
|
110
|
+
line_number: number
|
111
|
+
)
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
class Assertion
|
118
|
+
def self.parse(text:, line_number:)
|
119
|
+
parser = Parser.new(text)
|
120
|
+
|
121
|
+
new(
|
122
|
+
message: parser.message,
|
123
|
+
column_range: parser.column_range,
|
124
|
+
line_number: line_number
|
125
|
+
)
|
126
|
+
end
|
127
|
+
|
128
|
+
include Anima.new(:message, :column_range, :line_number),
|
129
|
+
Adamantium,
|
130
|
+
Comparable
|
131
|
+
|
132
|
+
def <=>(other)
|
133
|
+
to_a <=> other.to_a
|
134
|
+
end
|
135
|
+
|
136
|
+
protected
|
137
|
+
|
138
|
+
def to_a
|
139
|
+
[line_number, column_range.first, column_range.last, message]
|
140
|
+
end
|
141
|
+
|
142
|
+
class Parser
|
143
|
+
COLUMN_PATTERN = /^ *(?<carets>\^\^*) (?<message>.+)$/
|
144
|
+
|
145
|
+
include Concord.new(:text), Adamantium
|
146
|
+
|
147
|
+
def column_range
|
148
|
+
Range.new(*match.offset(:carets), true)
|
149
|
+
end
|
150
|
+
|
151
|
+
def message
|
152
|
+
match[:message]
|
153
|
+
end
|
154
|
+
|
155
|
+
private
|
156
|
+
|
157
|
+
def match
|
158
|
+
text.match(COLUMN_PATTERN)
|
159
|
+
end
|
160
|
+
memoize :match
|
161
|
+
end
|
162
|
+
|
163
|
+
private_constant(*constants(false))
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|