rubocop-rspec 1.6.0 → 1.7.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +22 -0
- data/README.md +23 -0
- data/Rakefile +19 -1
- data/config/default.yml +78 -15
- data/lib/rubocop-rspec.rb +19 -1
- data/lib/rubocop/cop/rspec/any_instance.rb +5 -1
- data/lib/rubocop/cop/rspec/be_eql.rb +58 -0
- data/lib/rubocop/cop/rspec/describe_class.rb +2 -3
- data/lib/rubocop/cop/rspec/describe_method.rb +4 -3
- data/lib/rubocop/cop/rspec/described_class.rb +4 -35
- data/lib/rubocop/cop/rspec/empty_example_group.rb +100 -0
- data/lib/rubocop/cop/rspec/example_length.rb +5 -2
- data/lib/rubocop/cop/rspec/example_wording.rb +5 -2
- data/lib/rubocop/cop/rspec/expect_actual.rb +79 -0
- data/lib/rubocop/cop/rspec/file_path.rb +3 -1
- data/lib/rubocop/cop/rspec/focus.rb +12 -28
- data/lib/rubocop/cop/rspec/hook_argument.rb +122 -0
- data/lib/rubocop/cop/rspec/instance_variable.rb +53 -12
- data/lib/rubocop/cop/rspec/leading_subject.rb +58 -0
- data/lib/rubocop/cop/rspec/let_setup.rb +58 -0
- data/lib/rubocop/cop/rspec/message_chain.rb +33 -0
- data/lib/rubocop/cop/rspec/message_expectation.rb +58 -0
- data/lib/rubocop/cop/rspec/multiple_describes.rb +10 -7
- data/lib/rubocop/cop/rspec/multiple_expectations.rb +89 -0
- data/lib/rubocop/cop/rspec/named_subject.rb +10 -1
- data/lib/rubocop/cop/rspec/nested_groups.rb +125 -0
- data/lib/rubocop/cop/rspec/not_to_not.rb +3 -3
- data/lib/rubocop/cop/rspec/subject_stub.rb +136 -0
- data/lib/rubocop/cop/rspec/verified_doubles.rb +4 -1
- data/lib/rubocop/rspec.rb +10 -0
- data/lib/rubocop/rspec/config_formatter.rb +33 -0
- data/lib/rubocop/rspec/description_extractor.rb +35 -0
- data/lib/rubocop/rspec/inject.rb +2 -6
- data/lib/rubocop/rspec/language.rb +73 -0
- data/lib/rubocop/rspec/language/node_pattern.rb +16 -0
- data/lib/rubocop/rspec/spec_only.rb +61 -0
- data/lib/rubocop/rspec/top_level_describe.rb +6 -0
- data/lib/rubocop/rspec/version.rb +1 -1
- data/rubocop-rspec.gemspec +1 -0
- data/spec/project/changelog_spec.rb +81 -0
- data/spec/project/default_config_spec.rb +52 -0
- data/spec/project/project_requires_spec.rb +8 -0
- data/spec/rubocop/cop/rspec/be_eql_spec.rb +59 -0
- data/spec/rubocop/cop/rspec/described_class_spec.rb +2 -2
- data/spec/rubocop/cop/rspec/empty_example_group_spec.rb +79 -0
- data/spec/rubocop/cop/rspec/example_length_spec.rb +50 -30
- data/spec/rubocop/cop/rspec/example_wording_spec.rb +21 -3
- data/spec/rubocop/cop/rspec/expect_actual_spec.rb +136 -0
- data/spec/rubocop/cop/rspec/file_path_spec.rb +48 -71
- data/spec/rubocop/cop/rspec/focus_spec.rb +1 -1
- data/spec/rubocop/cop/rspec/hook_argument_spec.rb +189 -0
- data/spec/rubocop/cop/rspec/instance_variable_spec.rb +37 -0
- data/spec/rubocop/cop/rspec/leading_subject_spec.rb +54 -0
- data/spec/rubocop/cop/rspec/let_setup_spec.rb +66 -0
- data/spec/rubocop/cop/rspec/message_chain_spec.rb +21 -0
- data/spec/rubocop/cop/rspec/message_expectation_spec.rb +63 -0
- data/spec/rubocop/cop/rspec/multiple_expectations_spec.rb +84 -0
- data/spec/rubocop/cop/rspec/nested_groups_spec.rb +55 -0
- data/spec/rubocop/cop/rspec/not_to_not_spec.rb +12 -2
- data/spec/rubocop/cop/rspec/subject_stub_spec.rb +183 -0
- data/spec/rubocop/rspec/config_formatter_spec.rb +48 -0
- data/spec/rubocop/rspec/description_extractor_spec.rb +35 -0
- data/spec/rubocop/rspec/language/selector_set_spec.rb +29 -0
- data/spec/rubocop/rspec/spec_only_spec.rb +97 -0
- data/spec/shared/rspec_only_cop_behavior.rb +68 -0
- data/spec/spec_helper.rb +13 -1
- metadata +72 -5
- data/spec/project_spec.rb +0 -115
data/rubocop-rspec.gemspec
CHANGED
@@ -0,0 +1,81 @@
|
|
1
|
+
describe 'CHANGELOG.md' do
|
2
|
+
subject(:changelog) { SpecHelper::ROOT.join('CHANGELOG.md').read }
|
3
|
+
|
4
|
+
it 'has link definitions for all implicit links' do
|
5
|
+
implicit_link_names = changelog.scan(/\[([^\]]+)\]\[\]/).flatten.uniq
|
6
|
+
implicit_link_names.each do |name|
|
7
|
+
expect(changelog).to include("[#{name}]: http")
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
describe 'entry' do
|
12
|
+
subject(:entries) { lines.grep(/^\*/).map(&:chomp) }
|
13
|
+
let(:lines) { changelog.each_line }
|
14
|
+
|
15
|
+
it 'has a whitespace between the * and the body' do
|
16
|
+
entries.each do |entry|
|
17
|
+
expect(entry).to match(/^\* \S/)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'has a link to the contributors at the end' do
|
22
|
+
entries.each do |entry|
|
23
|
+
expect(entry).to match(/\(\[@\S+\]\[\](?:, \[@\S+\]\[\])*\)$/)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
describe 'link to related issue on github' do
|
28
|
+
let(:issues) do
|
29
|
+
entries.map do |entry|
|
30
|
+
entry.match(/\[(?<number>[#\d]+)\]\((?<url>[^\)]+)\)/)
|
31
|
+
end.compact
|
32
|
+
end
|
33
|
+
|
34
|
+
it 'has an issue number prefixed with #' do
|
35
|
+
issues.each do |issue|
|
36
|
+
expect(issue[:number]).to match(/^#\d+$/)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
it 'has a valid URL' do
|
41
|
+
issues.each do |issue|
|
42
|
+
number = issue[:number].gsub(/\D/, '')
|
43
|
+
pattern = %r{^https://github\.com/.+/.+/(?:issues|pull)/#{number}$} # rubocop:disable LineLength
|
44
|
+
expect(issue[:url]).to match(pattern)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
it 'has a colon and a whitespace at the end' do
|
49
|
+
entries_including_issue_link = entries.select do |entry|
|
50
|
+
entry.match(/^\*\s*\[/)
|
51
|
+
end
|
52
|
+
|
53
|
+
entries_including_issue_link.each do |entry|
|
54
|
+
expect(entry).to include('): ')
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
describe 'body' do
|
60
|
+
let(:bodies) do
|
61
|
+
entries.map do |entry|
|
62
|
+
entry
|
63
|
+
.sub(/^\*\s*(?:\[.+?\):\s*)?/, '')
|
64
|
+
.sub(/\s*\([^\)]+\)$/, '')
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
it 'does not start with a lower case' do
|
69
|
+
bodies.each do |body|
|
70
|
+
expect(body).not_to match(/^[a-z]/)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
it 'ends with a punctuation' do
|
75
|
+
bodies.each do |body|
|
76
|
+
expect(body).to match(/[\.\!]$/)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
describe 'config/default.yml' do
|
2
|
+
subject(:default_config) do
|
3
|
+
RuboCop::ConfigLoader.load_file('config/default.yml')
|
4
|
+
end
|
5
|
+
|
6
|
+
let(:cop_names) do
|
7
|
+
glob = SpecHelper::ROOT.join('lib', 'rubocop', 'cop', 'rspec', '*.rb')
|
8
|
+
|
9
|
+
Pathname.glob(glob).map do |file|
|
10
|
+
file_name = file.basename('.rb').to_s
|
11
|
+
cop_name = file_name.gsub(/(^|_)(.)/) { Regexp.last_match(2).upcase }
|
12
|
+
|
13
|
+
"RSpec/#{cop_name}"
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
let(:config_keys) do
|
18
|
+
cop_names + %w(AllCops)
|
19
|
+
end
|
20
|
+
|
21
|
+
def cop_configuration(config_key)
|
22
|
+
cop_names.map do |cop_name|
|
23
|
+
cop_config = default_config.fetch(cop_name)
|
24
|
+
|
25
|
+
cop_config.fetch(config_key) do
|
26
|
+
raise "Expected #{cop_name} to have #{config_key} configuration key"
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'has configuration for all cops' do
|
32
|
+
expect(default_config.keys.sort).to eq(config_keys.sort)
|
33
|
+
end
|
34
|
+
|
35
|
+
it 'has descriptions for all cops' do
|
36
|
+
expect(cop_configuration('Description')).to all(be_a(String))
|
37
|
+
end
|
38
|
+
|
39
|
+
it 'does not have newlines in cop descriptions' do
|
40
|
+
cop_configuration('Description').each do |value|
|
41
|
+
expect(value).not_to include("\n")
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
it 'ends every description with a period' do
|
46
|
+
expect(cop_configuration('Description')).to all(end_with('.'))
|
47
|
+
end
|
48
|
+
|
49
|
+
it 'includes Enabled: true for every cop' do
|
50
|
+
expect(cop_configuration('Enabled')).to all(be(true))
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
describe RuboCop::Cop::RSpec::BeEql do
|
2
|
+
subject(:cop) { described_class.new }
|
3
|
+
|
4
|
+
it 'registers an offense for `eql` when argument is a boolean' do
|
5
|
+
expect_violation(<<-RUBY)
|
6
|
+
it { expect(foo).to eql(true) }
|
7
|
+
^^^ Prefer `be` over `eql`
|
8
|
+
it { expect(foo).to eql(false) }
|
9
|
+
^^^ Prefer `be` over `eql`
|
10
|
+
RUBY
|
11
|
+
end
|
12
|
+
|
13
|
+
it 'registers an offense for `eql` when argument is an integer' do
|
14
|
+
expect_violation(<<-RUBY)
|
15
|
+
it { expect(foo).to eql(0) }
|
16
|
+
^^^ Prefer `be` over `eql`
|
17
|
+
it { expect(foo).to eql(123) }
|
18
|
+
^^^ Prefer `be` over `eql`
|
19
|
+
RUBY
|
20
|
+
end
|
21
|
+
|
22
|
+
it 'registers an offense for `eql` when argument is a float' do
|
23
|
+
expect_violation(<<-RUBY)
|
24
|
+
it { expect(foo).to eql(1.0) }
|
25
|
+
^^^ Prefer `be` over `eql`
|
26
|
+
it { expect(foo).to eql(1.23) }
|
27
|
+
^^^ Prefer `be` over `eql`
|
28
|
+
RUBY
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'registers an offense for `eql` when argument is a symbol' do
|
32
|
+
expect_violation(<<-RUBY)
|
33
|
+
it { expect(foo).to eql(:foo) }
|
34
|
+
^^^ Prefer `be` over `eql`
|
35
|
+
RUBY
|
36
|
+
end
|
37
|
+
|
38
|
+
it 'does not register an offense for `eql` when argument is a string' do
|
39
|
+
expect_no_violations(<<-RUBY)
|
40
|
+
it { expect(foo).to eql('foo') }
|
41
|
+
RUBY
|
42
|
+
end
|
43
|
+
|
44
|
+
it 'does not register an offense for `eql` when expectation is negated' do
|
45
|
+
expect_no_violations(<<-RUBY)
|
46
|
+
it { expect(foo).to_not eql(1) }
|
47
|
+
RUBY
|
48
|
+
end
|
49
|
+
|
50
|
+
it 'autocorrects offense to use `be`' do
|
51
|
+
corrected =
|
52
|
+
autocorrect_source(
|
53
|
+
cop,
|
54
|
+
['it { expect(foo).to eql(1) }'],
|
55
|
+
'spec/foo_spec.rb'
|
56
|
+
)
|
57
|
+
expect(corrected).to eq 'it { expect(foo).to be(1) }'
|
58
|
+
end
|
59
|
+
end
|
@@ -92,7 +92,7 @@ describe RuboCop::Cop::RSpec::DescribedClass, :config do
|
|
92
92
|
RUBY
|
93
93
|
end
|
94
94
|
|
95
|
-
it 'ignores describe that do not
|
95
|
+
it 'ignores describe that do not reference to a class' do
|
96
96
|
expect_no_violations(<<-RUBY)
|
97
97
|
describe "MyClass" do
|
98
98
|
subject { "MyClass" }
|
@@ -204,7 +204,7 @@ describe RuboCop::Cop::RSpec::DescribedClass, :config do
|
|
204
204
|
' subject { MyClass.do_something }',
|
205
205
|
' before { MyClass.do_something }',
|
206
206
|
'end'
|
207
|
-
]
|
207
|
+
], 'spec/foo_spec.rb'
|
208
208
|
)
|
209
209
|
expect(new_source).to eq(
|
210
210
|
[
|
@@ -0,0 +1,79 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
describe RuboCop::Cop::RSpec::EmptyExampleGroup, :config do
|
4
|
+
subject(:cop) { described_class.new(config) }
|
5
|
+
|
6
|
+
it 'flags an empty context' do
|
7
|
+
expect_violation(<<-RUBY)
|
8
|
+
describe Foo do
|
9
|
+
context 'when bar' do
|
10
|
+
^^^^^^^^^^^^^^^^^^ Empty example group detected.
|
11
|
+
|
12
|
+
let(:foo) { bar }
|
13
|
+
end
|
14
|
+
|
15
|
+
describe '#thingy?' do
|
16
|
+
specify do
|
17
|
+
expect(whatever.thingy?).to be(true)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
it { should be_true }
|
22
|
+
end
|
23
|
+
RUBY
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'flags an empty top level describe' do
|
27
|
+
expect_violation(<<-RUBY)
|
28
|
+
describe Foo do
|
29
|
+
^^^^^^^^^^^^ Empty example group detected.
|
30
|
+
end
|
31
|
+
RUBY
|
32
|
+
end
|
33
|
+
|
34
|
+
it 'does not flag include_examples' do
|
35
|
+
expect_no_violations(<<-RUBY)
|
36
|
+
describe Foo do
|
37
|
+
context "when something is true" do
|
38
|
+
include_examples "some expectations"
|
39
|
+
end
|
40
|
+
|
41
|
+
context "when something else is true" do
|
42
|
+
include_context "some expectations"
|
43
|
+
end
|
44
|
+
|
45
|
+
context "when a third thing is true" do
|
46
|
+
it_behaves_like "some thingy"
|
47
|
+
end
|
48
|
+
end
|
49
|
+
RUBY
|
50
|
+
end
|
51
|
+
|
52
|
+
it 'does not recognize custom include methods by default' do
|
53
|
+
expect_violation(<<-RUBY)
|
54
|
+
describe Foo do
|
55
|
+
^^^^^^^^^^^^ Empty example group detected.
|
56
|
+
context "when I do something clever" do
|
57
|
+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Empty example group detected.
|
58
|
+
it_has_special_behavior
|
59
|
+
end
|
60
|
+
end
|
61
|
+
RUBY
|
62
|
+
end
|
63
|
+
|
64
|
+
context 'when a custom include method is specified' do
|
65
|
+
let(:cop_config) do
|
66
|
+
{ 'CustomIncludeMethods' => %w(it_has_special_behavior) }
|
67
|
+
end
|
68
|
+
|
69
|
+
it 'does not flag an otherwise empty example group' do
|
70
|
+
expect_no_violations(<<-RUBY)
|
71
|
+
describe Foo do
|
72
|
+
context "when I do something clever" do
|
73
|
+
it_has_special_behavior
|
74
|
+
end
|
75
|
+
end
|
76
|
+
RUBY
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -1,5 +1,6 @@
|
|
1
1
|
describe RuboCop::Cop::RSpec::ExampleLength, :config do
|
2
2
|
subject(:cop) { described_class.new(config) }
|
3
|
+
|
3
4
|
let(:cop_config) { { 'Max' => 3 } }
|
4
5
|
|
5
6
|
it 'ignores non-spec blocks' do
|
@@ -12,7 +13,8 @@ describe RuboCop::Cop::RSpec::ExampleLength, :config do
|
|
12
13
|
' line 3',
|
13
14
|
' line 4',
|
14
15
|
'end'
|
15
|
-
]
|
16
|
+
],
|
17
|
+
'foo_spec.rb'
|
16
18
|
)
|
17
19
|
|
18
20
|
expect(cop.offenses).to be_empty
|
@@ -24,7 +26,8 @@ describe RuboCop::Cop::RSpec::ExampleLength, :config do
|
|
24
26
|
[
|
25
27
|
'it do',
|
26
28
|
'end'
|
27
|
-
]
|
29
|
+
],
|
30
|
+
'foo_spec.rb'
|
28
31
|
)
|
29
32
|
expect(cop.offenses).to be_empty
|
30
33
|
end
|
@@ -38,60 +41,77 @@ describe RuboCop::Cop::RSpec::ExampleLength, :config do
|
|
38
41
|
' line 2',
|
39
42
|
' line 3',
|
40
43
|
'end'
|
41
|
-
]
|
44
|
+
],
|
45
|
+
'spec/foo_spec.rb'
|
42
46
|
)
|
43
47
|
expect(cop.offenses).to be_empty
|
44
48
|
end
|
45
49
|
|
46
|
-
it
|
50
|
+
it 'ignores comments' do
|
47
51
|
inspect_source(
|
48
52
|
cop,
|
49
53
|
[
|
50
54
|
'it do',
|
51
55
|
' line 1',
|
52
56
|
' line 2',
|
57
|
+
' # comment',
|
53
58
|
' line 3',
|
54
|
-
' line 4',
|
55
59
|
'end'
|
56
|
-
]
|
60
|
+
],
|
61
|
+
'spec/foo_spec.rb'
|
57
62
|
)
|
58
|
-
expect(cop.offenses
|
59
|
-
expect(cop.offenses.map(&:line).sort).to eq([1])
|
60
|
-
expect(cop.messages).to eq(['Example has too many lines. [4/3]'])
|
63
|
+
expect(cop.offenses).to be_empty
|
61
64
|
end
|
62
65
|
|
63
|
-
|
64
|
-
|
65
|
-
cop,
|
66
|
+
shared_examples 'large example violation' do
|
67
|
+
before do
|
68
|
+
inspect_source(cop, source, 'spec/foo_spec.rb')
|
69
|
+
end
|
70
|
+
|
71
|
+
it 'flags an offense' do
|
72
|
+
expect(cop.offenses.size).to eq(1)
|
73
|
+
end
|
74
|
+
|
75
|
+
it 'registers the offense on line 1' do
|
76
|
+
expect(cop.offenses.map(&:line)).to eq([1])
|
77
|
+
end
|
78
|
+
|
79
|
+
it 'adds a message saying the example has too many lines' do
|
80
|
+
expect(cop.messages).to eq(['Example has too many lines. [4/3]'])
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
context 'when inspecting large examples' do
|
85
|
+
let(:source) do
|
66
86
|
[
|
67
87
|
'it do',
|
68
88
|
' line 1',
|
69
89
|
' line 2',
|
70
|
-
' # comment',
|
71
90
|
' line 3',
|
91
|
+
' line 4',
|
72
92
|
'end'
|
73
93
|
]
|
74
|
-
|
75
|
-
|
94
|
+
end
|
95
|
+
|
96
|
+
include_examples 'large example violation'
|
76
97
|
end
|
77
98
|
|
78
99
|
context 'with CountComments enabled' do
|
79
|
-
|
100
|
+
let(:cop_config) do
|
101
|
+
{ 'Max' => 3, 'CountComments' => true }
|
102
|
+
end
|
80
103
|
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
]
|
91
|
-
)
|
92
|
-
expect(cop.offenses.size).to eq(1)
|
93
|
-
expect(cop.offenses.map(&:line).sort).to eq([1])
|
94
|
-
expect(cop.messages).to eq(['Example has too many lines. [4/3]'])
|
104
|
+
let(:source) do
|
105
|
+
[
|
106
|
+
'it do',
|
107
|
+
' line 1',
|
108
|
+
' line 2',
|
109
|
+
' # comment',
|
110
|
+
' line 3',
|
111
|
+
'end'
|
112
|
+
]
|
95
113
|
end
|
114
|
+
|
115
|
+
include_examples 'large example violation'
|
96
116
|
end
|
97
117
|
end
|