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.
@@ -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
- inspect_source(
6
- cop,
7
- [
8
- 'describe MyClass do',
9
- ' before { @foo = [] }',
10
- ' it { expect(@foo).to be_empty }',
11
- 'end'
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
- inspect_source(
21
- cop,
22
- [
23
- 'not_rspec do',
24
- ' before { @foo = [] }',
25
- ' it { expect(@foo).to be_empty }',
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
- inspect_source(
34
- cop,
35
- [
36
- "shared_examples 'shared example' do",
37
- ' it { expect(@foo).to be_empty }',
38
- 'end'
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
- inspect_source(cop, ['@foo = []', '@foo.empty?'])
48
- expect(cop.offenses).to be_empty
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
- inspect_source(
6
- cop,
7
- [
8
- "describe MyClass, '.do_something' do; end",
9
- "describe MyClass, '.do_something_else' do; end"
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
- inspect_source(
20
- cop,
21
- [
22
- 'describe MyClass do; end',
23
- 'describe MyOtherClass do; end'
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
- inspect_source(
34
- cop,
35
- [
36
- "require 'spec_helper'",
37
- '',
38
- 'describe MyClass do; end'
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
- inspect_source(subject, 'it { expect(false).to_not be_true }')
9
-
10
- expect(subject.messages).to eq(['Prefer `not_to` over `to_not`'])
11
- expect(subject.highlights).to eq(['expect(false).to_not be_true'])
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
- inspect_source(subject, 'it { expect(false).not_to be_true }')
17
-
18
- expect(subject.messages).to be_empty
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
- inspect_source(subject, 'it { expect(false).not_to be_true }')
32
-
33
- expect(subject.messages).to eq(['Prefer `to_not` over `not_to`'])
34
- expect(subject.highlights).to eq(['expect(false).not_to be_true'])
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
- inspect_source(subject, 'it { expect(false).to_not be_true }')
40
-
41
- expect(subject.messages).to be_empty
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
- inspect_source(cop, ['it do',
6
- ' foo = double("Widget")',
7
- 'end'])
8
- expect(cop.messages)
9
- .to eq(['Prefer using verifying doubles over normal doubles.'])
10
- expect(cop.highlights).to eq(['double("Widget")'])
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
- inspect_source(cop, ['it do',
22
- ' foo = double(:widget)',
23
- 'end'])
24
- expect(cop.messages)
25
- .to eq(['Prefer using verifying doubles over normal doubles.'])
26
- expect(cop.highlights).to eq(['double(:widget)'])
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
- inspect_source(cop, ['it do',
32
- ' foo = spy("Widget")',
33
- 'end'])
34
- expect(cop.messages)
35
- .to eq(['Prefer using verifying doubles over normal doubles.'])
36
- expect(cop.highlights).to eq(['spy("Widget")'])
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
- inspect_source(cop, ['it do',
46
- ' foo = double(:widget)',
47
- 'end'])
48
- expect(cop.messages).to be_empty
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
- inspect_source(cop, ['it do',
53
- ' foo = double("widget")',
54
- 'end'])
55
-
56
- expect(cop.messages.first).to eq(
57
- 'Prefer using verifying doubles over normal doubles.'
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
- inspect_source(cop, ['it do',
64
- ' foo = double',
65
- 'end'])
66
- expect(cop.messages).to be_empty
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
- inspect_source(cop, ['it do',
71
- ' foo = instance_double("Foo")',
72
- 'end'])
73
- expect(cop.messages).to be_empty
65
+ expect_no_violations(<<-RUBY)
66
+ it do
67
+ foo = instance_double("Foo")
68
+ end
69
+ RUBY
74
70
  end
75
71
  end
@@ -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