rubocop-rspec 1.8.0 → 1.9.0

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.
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