rubocop-rspec 1.8.0 → 1.9.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (62) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +8 -0
  3. data/Gemfile +2 -1
  4. data/Rakefile +3 -1
  5. data/config/default.yml +16 -0
  6. data/lib/rubocop-rspec.rb +6 -1
  7. data/lib/rubocop/cop/rspec/any_instance.rb +0 -2
  8. data/lib/rubocop/cop/rspec/be_eql.rb +1 -2
  9. data/lib/rubocop/cop/rspec/cop.rb +66 -0
  10. data/lib/rubocop/cop/rspec/describe_class.rb +6 -1
  11. data/lib/rubocop/cop/rspec/describe_method.rb +1 -2
  12. data/lib/rubocop/cop/rspec/described_class.rb +3 -6
  13. data/lib/rubocop/cop/rspec/empty_example_group.rb +1 -11
  14. data/lib/rubocop/cop/rspec/example_length.rb +1 -1
  15. data/lib/rubocop/cop/rspec/example_wording.rb +0 -2
  16. data/lib/rubocop/cop/rspec/expect_actual.rb +0 -2
  17. data/lib/rubocop/cop/rspec/file_path.rb +1 -1
  18. data/lib/rubocop/cop/rspec/focus.rb +4 -9
  19. data/lib/rubocop/cop/rspec/hook_argument.rb +3 -5
  20. data/lib/rubocop/cop/rspec/implicit_expect.rb +1 -1
  21. data/lib/rubocop/cop/rspec/instance_variable.rb +1 -5
  22. data/lib/rubocop/cop/rspec/leading_subject.rb +0 -2
  23. data/lib/rubocop/cop/rspec/let_setup.rb +1 -4
  24. data/lib/rubocop/cop/rspec/message_chain.rb +1 -8
  25. data/lib/rubocop/cop/rspec/message_expectation.rb +1 -1
  26. data/lib/rubocop/cop/rspec/message_spies.rb +79 -0
  27. data/lib/rubocop/cop/rspec/multiple_describes.rb +1 -2
  28. data/lib/rubocop/cop/rspec/multiple_expectations.rb +2 -6
  29. data/lib/rubocop/cop/rspec/named_subject.rb +0 -2
  30. data/lib/rubocop/cop/rspec/nested_groups.rb +2 -6
  31. data/lib/rubocop/cop/rspec/not_to_not.rb +1 -2
  32. data/lib/rubocop/cop/rspec/repeated_description.rb +59 -0
  33. data/lib/rubocop/cop/rspec/single_argument_message_chain.rb +48 -0
  34. data/lib/rubocop/cop/rspec/subject_stub.rb +1 -4
  35. data/lib/rubocop/cop/rspec/verified_doubles.rb +0 -2
  36. data/lib/rubocop/rspec.rb +1 -1
  37. data/lib/rubocop/rspec/description_extractor.rb +55 -18
  38. data/lib/rubocop/rspec/example.rb +56 -0
  39. data/lib/rubocop/rspec/example_group.rb +57 -0
  40. data/lib/rubocop/rspec/language.rb +28 -1
  41. data/lib/rubocop/rspec/language/node_pattern.rb +1 -3
  42. data/lib/rubocop/rspec/top_level_describe.rb +6 -10
  43. data/lib/rubocop/rspec/version.rb +1 -1
  44. data/spec/project/default_config_spec.rb +8 -5
  45. data/spec/project/project_requires_spec.rb +1 -1
  46. data/spec/rubocop/{rspec/spec_only_spec.rb → cop/rspec/cop_spec.rb} +2 -4
  47. data/spec/rubocop/cop/rspec/describe_class_spec.rb +36 -0
  48. data/spec/rubocop/cop/rspec/described_class_spec.rb +2 -2
  49. data/spec/rubocop/cop/rspec/example_length_spec.rb +48 -60
  50. data/spec/rubocop/cop/rspec/implicit_expect_spec.rb +1 -1
  51. data/spec/rubocop/cop/rspec/message_spies_spec.rb +93 -0
  52. data/spec/rubocop/cop/rspec/repeated_description_spec.rb +76 -0
  53. data/spec/rubocop/cop/rspec/single_argument_message_chain_spec.rb +75 -0
  54. data/spec/rubocop/rspec/description_extractor_spec.rb +46 -24
  55. data/spec/rubocop/rspec/example_group_spec.rb +44 -0
  56. data/spec/rubocop/rspec/example_spec.rb +62 -0
  57. data/spec/rubocop/rspec/language/selector_set_spec.rb +22 -2
  58. data/spec/spec_helper.rb +2 -9
  59. data/spec/support/expect_violation.rb +4 -2
  60. metadata +21 -8
  61. data/lib/rubocop/rspec/spec_only.rb +0 -61
  62. data/spec/shared/rspec_only_cop_behavior.rb +0 -68
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module RSpec
5
+ # Wrapper for RSpec example groups
6
+ class ExampleGroup
7
+ include Language
8
+ extend NodePattern::Macros
9
+
10
+ def_node_matcher :example?, Examples::ALL.block_pattern
11
+
12
+ # @!method scope_change?(node)
13
+ #
14
+ # Detect if the node is an example group or shared example
15
+ #
16
+ # Selectors which indicate that we should stop searching
17
+ #
18
+ def_node_matcher :scope_change?,
19
+ (ExampleGroups::ALL + SharedGroups::ALL).block_pattern
20
+
21
+ def initialize(node)
22
+ @node = node
23
+ end
24
+
25
+ def examples
26
+ examples_in_scope(node).map(&Example.public_method(:new))
27
+ end
28
+
29
+ private
30
+
31
+ attr_reader :node
32
+
33
+ def examples_in_scope(node)
34
+ node.each_child_node.flat_map do |child|
35
+ find_examples(child)
36
+ end
37
+ end
38
+
39
+ # Recursively search for examples within the current scope
40
+ #
41
+ # Searches node for examples and halts when a scope change is detected
42
+ #
43
+ # @param node [RuboCop::Node] node to recursively search for examples
44
+ #
45
+ # @return [Array<RuboCop::Node>] discovered example nodes
46
+ def find_examples(node)
47
+ return [] if scope_change?(node)
48
+
49
+ if example?(node)
50
+ [node]
51
+ else
52
+ examples_in_scope(node)
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
@@ -22,7 +22,19 @@ module RuboCop
22
22
  selectors.include?(selector)
23
23
  end
24
24
 
25
- def to_node_pattern
25
+ def block_pattern
26
+ "(block #{send_pattern} ...)"
27
+ end
28
+
29
+ def send_pattern
30
+ "(send _ #{node_pattern_union} ...)"
31
+ end
32
+
33
+ def node_pattern_union
34
+ "{#{node_pattern}}"
35
+ end
36
+
37
+ def node_pattern
26
38
  selectors.map(&:inspect).join(' ')
27
39
  end
28
40
 
@@ -31,6 +43,10 @@ module RuboCop
31
43
  attr_reader :selectors
32
44
  end
33
45
 
46
+ module Matchers
47
+ MESSAGE_CHAIN = SelectorSet.new(%i(receive_message_chain stub_chain))
48
+ end
49
+
34
50
  module ExampleGroups
35
51
  GROUPS = SelectorSet.new(%i(describe context feature example_group))
36
52
  SKIPPED = SelectorSet.new(%i(xdescribe xcontext xfeature))
@@ -45,6 +61,17 @@ module RuboCop
45
61
  )
46
62
  end
47
63
 
64
+ module Includes
65
+ ALL = SelectorSet.new(
66
+ %i(
67
+ it_behaves_like
68
+ it_should_behave_like
69
+ include_context
70
+ include_examples
71
+ )
72
+ )
73
+ end
74
+
48
75
  module Examples
49
76
  EXAMPLES = SelectorSet.new(%i(it specify example scenario its))
50
77
  FOCUSED = SelectorSet.new(%i(fit fspecify fexample fscenario focus))
@@ -7,9 +7,7 @@ module RuboCop
7
7
  module NodePattern
8
8
  extend RuboCop::NodePattern::Macros
9
9
 
10
- def_node_matcher :example_group?, <<-PATTERN
11
- (block (send _ {#{ExampleGroups::ALL.to_node_pattern}} ...) ...)
12
- PATTERN
10
+ def_node_matcher :example_group?, ExampleGroups::ALL.block_pattern
13
11
  end
14
12
  end
15
13
  end
@@ -31,9 +31,9 @@ module RuboCop
31
31
  # If we have no top level describe statements, we need to check any
32
32
  # blocks on the top level (e.g. after a require).
33
33
  if nodes.empty?
34
- nodes = node_children(root_node).map do |child|
35
- describe_statement_children(child) if child.type == :block
36
- end.flatten.compact
34
+ nodes = root_node.each_child_node(:block).flat_map do |child|
35
+ describe_statement_children(child)
36
+ end
37
37
  end
38
38
 
39
39
  nodes
@@ -44,18 +44,14 @@ module RuboCop
44
44
  end
45
45
 
46
46
  def single_top_level_describe?
47
- top_level_nodes.count == 1
47
+ top_level_nodes.one?
48
48
  end
49
49
 
50
50
  def describe_statement_children(node)
51
- node_children(node).select do |element|
52
- element.type == :send && element.children[1] == :describe
51
+ node.each_child_node(:send).select do |element|
52
+ element.children[1] == :describe
53
53
  end
54
54
  end
55
-
56
- def node_children(node)
57
- node.children.select { |e| e.is_a? Parser::AST::Node }
58
- end
59
55
  end
60
56
  end
61
57
  end
@@ -4,7 +4,7 @@ module RuboCop
4
4
  module RSpec
5
5
  # Version information for the RSpec RuboCop plugin.
6
6
  module Version
7
- STRING = '1.8.0'.freeze
7
+ STRING = '1.9.0'.freeze
8
8
  end
9
9
  end
10
10
  end
@@ -6,12 +6,15 @@ describe 'config/default.yml' do
6
6
  let(:cop_names) do
7
7
  glob = SpecHelper::ROOT.join('lib', 'rubocop', 'cop', 'rspec', '*.rb')
8
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 }
9
+ cop_names =
10
+ Pathname.glob(glob).map do |file|
11
+ file_name = file.basename('.rb').to_s
12
+ cop_name = file_name.gsub(/(^|_)(.)/) { Regexp.last_match(2).upcase }
12
13
 
13
- "RSpec/#{cop_name}"
14
- end
14
+ "RSpec/#{cop_name}"
15
+ end
16
+
17
+ cop_names - %w(RSpec/Cop)
15
18
  end
16
19
 
17
20
  let(:config_keys) do
@@ -1,7 +1,7 @@
1
1
  describe 'Project requires' do
2
2
  it 'alphabetizes cop requires' do
3
3
  source = SpecHelper::ROOT.join('lib', 'rubocop-rspec.rb').read
4
- requires = source.split("\n").grep(%r{rubocop/cop})
4
+ requires = source.split("\n").grep(%r{rubocop/cop/rspec/[^(?:cop)]})
5
5
 
6
6
  expect(requires.join("\n")).to eql(requires.sort.join("\n"))
7
7
  end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- RSpec.describe RuboCop::RSpec::SpecOnly do
3
+ RSpec.describe RuboCop::Cop::RSpec::Cop do
4
4
  subject(:cop) { fake_cop.new(config) }
5
5
 
6
6
  let(:config) do
@@ -20,9 +20,7 @@ RSpec.describe RuboCop::RSpec::SpecOnly do
20
20
  end
21
21
 
22
22
  let(:fake_cop) do
23
- Class.new(RuboCop::Cop::Cop) do
24
- include RuboCop::RSpec::SpecOnly
25
-
23
+ Class.new(described_class) do
26
24
  def self.name
27
25
  'RuboCop::RSpec::FakeCop'
28
26
  end
@@ -110,4 +110,40 @@ describe RuboCop::Cop::RSpec::DescribeClass do
110
110
  it "doesn't blow up on single-line describes" do
111
111
  expect_no_violations('describe Some::Class')
112
112
  end
113
+
114
+ it "doesn't flag top level describe in a shared example" do
115
+ expect_no_violations(<<-RUBY)
116
+ shared_examples 'Common::Interface' do
117
+ describe '#public_interface' do
118
+ it 'conforms to interface' do
119
+ # ...
120
+ end
121
+ end
122
+ end
123
+ RUBY
124
+ end
125
+
126
+ it "doesn't flag top level describe in a shared context" do
127
+ expect_no_violations(<<-RUBY)
128
+ RSpec.shared_context 'Common::Interface' do
129
+ describe '#public_interface' do
130
+ it 'conforms to interface' do
131
+ # ...
132
+ end
133
+ end
134
+ end
135
+ RUBY
136
+ end
137
+
138
+ it "doesn't flag top level describe in an unnamed shared context" do
139
+ expect_no_violations(<<-RUBY)
140
+ shared_context do
141
+ describe '#public_interface' do
142
+ it 'conforms to interface' do
143
+ # ...
144
+ end
145
+ end
146
+ end
147
+ RUBY
148
+ end
113
149
  end
@@ -160,7 +160,7 @@ describe RuboCop::Cop::RSpec::DescribedClass, :config do
160
160
  RUBY
161
161
  end
162
162
 
163
- it 'does not flag violations within a scope change' do
163
+ it 'does not flag violations within a class scope change' do
164
164
  expect_no_violations(<<-RUBY)
165
165
  describe MyNamespace::MyClass do
166
166
  before do
@@ -172,7 +172,7 @@ describe RuboCop::Cop::RSpec::DescribedClass, :config do
172
172
  RUBY
173
173
  end
174
174
 
175
- it 'does not flag violations within a scope change' do
175
+ it 'does not flag violations within a hook scope change' do
176
176
  expect_no_violations(<<-RUBY)
177
177
  describe do
178
178
  before do
@@ -4,63 +4,42 @@ describe RuboCop::Cop::RSpec::ExampleLength, :config do
4
4
  let(:cop_config) { { 'Max' => 3 } }
5
5
 
6
6
  it 'ignores non-spec blocks' do
7
- inspect_source(
8
- cop,
9
- [
10
- 'foo do',
11
- ' line 1',
12
- ' line 2',
13
- ' line 3',
14
- ' line 4',
15
- 'end'
16
- ],
17
- 'foo_spec.rb'
18
- )
19
-
20
- expect(cop.offenses).to be_empty
7
+ expect_no_violations(<<-RUBY)
8
+ foo do
9
+ line 1
10
+ line 2
11
+ line 3
12
+ line 4
13
+ end
14
+ RUBY
21
15
  end
22
16
 
23
17
  it 'allows an empty example' do
24
- inspect_source(
25
- cop,
26
- [
27
- 'it do',
28
- 'end'
29
- ],
30
- 'foo_spec.rb'
31
- )
32
- expect(cop.offenses).to be_empty
18
+ expect_no_violations(<<-RUBY)
19
+ it do
20
+ end
21
+ RUBY
33
22
  end
34
23
 
35
24
  it 'allows a short example' do
36
- inspect_source(
37
- cop,
38
- [
39
- 'it do',
40
- ' line 1',
41
- ' line 2',
42
- ' line 3',
43
- 'end'
44
- ],
45
- 'spec/foo_spec.rb'
46
- )
47
- expect(cop.offenses).to be_empty
25
+ expect_no_violations(<<-RUBY)
26
+ it do
27
+ line 1
28
+ line 2
29
+ line 3
30
+ end
31
+ RUBY
48
32
  end
49
33
 
50
34
  it 'ignores comments' do
51
- inspect_source(
52
- cop,
53
- [
54
- 'it do',
55
- ' line 1',
56
- ' line 2',
57
- ' # comment',
58
- ' line 3',
59
- 'end'
60
- ],
61
- 'spec/foo_spec.rb'
62
- )
63
- expect(cop.offenses).to be_empty
35
+ expect_no_violations(<<-RUBY)
36
+ it do
37
+ line 1
38
+ line 2
39
+ # comment
40
+ line 3
41
+ end
42
+ RUBY
64
43
  end
65
44
 
66
45
  shared_examples 'large example violation' do
@@ -82,18 +61,17 @@ describe RuboCop::Cop::RSpec::ExampleLength, :config do
82
61
  end
83
62
 
84
63
  context 'when inspecting large examples' do
85
- let(:source) do
86
- [
87
- 'it do',
88
- ' line 1',
89
- ' line 2',
90
- ' line 3',
91
- ' line 4',
92
- 'end'
93
- ]
64
+ it 'flags the example' do
65
+ expect_violation(<<-RUBY)
66
+ it do
67
+ ^^^^^ Example has too many lines. [4/3]
68
+ line 1
69
+ line 2
70
+ line 3
71
+ line 4
72
+ end
73
+ RUBY
94
74
  end
95
-
96
- include_examples 'large example violation'
97
75
  end
98
76
 
99
77
  context 'with CountComments enabled' do
@@ -112,6 +90,16 @@ describe RuboCop::Cop::RSpec::ExampleLength, :config do
112
90
  ]
113
91
  end
114
92
 
115
- include_examples 'large example violation'
93
+ it 'flags the example' do
94
+ expect_violation(<<-RUBY)
95
+ it do
96
+ ^^^^^ Example has too many lines. [4/3]
97
+ line 1
98
+ line 2
99
+ # comment
100
+ line 3
101
+ end
102
+ RUBY
103
+ end
116
104
  end
117
105
  end
@@ -30,7 +30,7 @@ describe RuboCop::Cop::RSpec::ImplicitExpect, :config do
30
30
  expect_no_violations('it { is_expected.to_not be_truthy }')
31
31
  end
32
32
 
33
- it 'approves of is_expected.to_not' do
33
+ it 'approves of is_expected.not_to' do
34
34
  expect_no_violations('it { is_expected.not_to be_truthy }')
35
35
  end
36
36
 
@@ -0,0 +1,93 @@
1
+ # frozen_string_literal: true
2
+
3
+ describe RuboCop::Cop::RSpec::MessageSpies, :config do
4
+ subject(:cop) { described_class.new(config) }
5
+
6
+ context 'when EnforcedStyle is have_received' do
7
+ let(:cop_config) do
8
+ { 'EnforcedStyle' => 'have_received' }
9
+ end
10
+
11
+ it 'flags expect(...).to receive' do
12
+ expect_violation(<<-RUBY)
13
+ expect(foo).to receive(:bar)
14
+ ^^^^^^^ Prefer `have_received` for setting message expectations. Setup `foo` as a spy using `allow` or `instance_spy`.
15
+ RUBY
16
+ end
17
+
18
+ it 'flags expect(...).to receive with' do
19
+ expect_violation(<<-RUBY)
20
+ expect(foo).to receive(:bar).with(:baz)
21
+ ^^^^^^^ Prefer `have_received` for setting message expectations. Setup `foo` as a spy using `allow` or `instance_spy`.
22
+ RUBY
23
+ end
24
+
25
+ it 'flags expect(...).to receive at_most' do
26
+ expect_violation(<<-RUBY)
27
+ expect(foo).to receive(:bar).at_most(42).times
28
+ ^^^^^^^ Prefer `have_received` for setting message expectations. Setup `foo` as a spy using `allow` or `instance_spy`.
29
+ RUBY
30
+ end
31
+
32
+ it 'approves of expect(...).to have_received' do
33
+ expect_no_violations('expect(foo).to have_received(:bar)')
34
+ end
35
+
36
+ it 'generates a todo based on the usage of the correct style' do
37
+ inspect_source(cop, 'expect(foo).to have_received(:bar)', 'foo_spec.rb')
38
+
39
+ expect(cop.config_to_allow_offenses)
40
+ .to eq('EnforcedStyle' => 'have_received')
41
+ end
42
+
43
+ it 'generates a todo based on the usage of the alternate style' do
44
+ inspect_source(cop, 'expect(foo).to receive(:bar)', 'foo_spec.rb')
45
+
46
+ expect(cop.config_to_allow_offenses).to eq('EnforcedStyle' => 'receive')
47
+ end
48
+ end
49
+
50
+ context 'when EnforcedStyle is receive' do
51
+ let(:cop_config) do
52
+ { 'EnforcedStyle' => 'receive' }
53
+ end
54
+
55
+ it 'flags expect(...).to have_received' do
56
+ expect_violation(<<-RUBY)
57
+ expect(foo).to have_received(:bar)
58
+ ^^^^^^^^^^^^^ Prefer `receive` for setting message expectations.
59
+ RUBY
60
+ end
61
+
62
+ it 'flags expect(...).to have_received with' do
63
+ expect_violation(<<-RUBY)
64
+ expect(foo).to have_received(:bar).with(:baz)
65
+ ^^^^^^^^^^^^^ Prefer `receive` for setting message expectations.
66
+ RUBY
67
+ end
68
+
69
+ it 'flags expect(...).to have_received at_most' do
70
+ expect_violation(<<-RUBY)
71
+ expect(foo).to have_received(:bar).at_most(42).times
72
+ ^^^^^^^^^^^^^ Prefer `receive` for setting message expectations.
73
+ RUBY
74
+ end
75
+
76
+ it 'approves of expect(...).to receive' do
77
+ expect_no_violations('expect(foo).to receive(:bar)')
78
+ end
79
+
80
+ it 'generates a todo based on the usage of the correct style' do
81
+ inspect_source(cop, 'expect(foo).to receive(:bar)', 'foo_spec.rb')
82
+
83
+ expect(cop.config_to_allow_offenses).to eq('EnforcedStyle' => 'receive')
84
+ end
85
+
86
+ it 'generates a todo based on the usage of the alternate style' do
87
+ inspect_source(cop, 'expect(foo).to have_received(:bar)', 'foo_spec.rb')
88
+
89
+ expect(cop.config_to_allow_offenses)
90
+ .to eq('EnforcedStyle' => 'have_received')
91
+ end
92
+ end
93
+ end