rubocop-rspec 1.9.1 → 1.10.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 (38) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +11 -0
  3. data/Gemfile +2 -1
  4. data/config/default.yml +13 -1
  5. data/lib/rubocop-rspec.rb +5 -0
  6. data/lib/rubocop/cop/rspec/any_instance.rb +1 -1
  7. data/lib/rubocop/cop/rspec/cop.rb +1 -2
  8. data/lib/rubocop/cop/rspec/described_class.rb +1 -1
  9. data/lib/rubocop/cop/rspec/expect_output.rb +52 -0
  10. data/lib/rubocop/cop/rspec/implicit_expect.rb +3 -2
  11. data/lib/rubocop/cop/rspec/message_chain.rb +1 -1
  12. data/lib/rubocop/cop/rspec/message_spies.rb +2 -2
  13. data/lib/rubocop/cop/rspec/multiple_expectations.rb +2 -4
  14. data/lib/rubocop/cop/rspec/named_subject.rb +1 -1
  15. data/lib/rubocop/cop/rspec/nested_groups.rb +16 -1
  16. data/lib/rubocop/cop/rspec/repeated_example.rb +41 -0
  17. data/lib/rubocop/cop/rspec/scattered_setup.rb +49 -0
  18. data/lib/rubocop/cop/rspec/single_argument_message_chain.rb +17 -5
  19. data/lib/rubocop/rspec.rb +1 -1
  20. data/lib/rubocop/rspec/concept.rb +33 -0
  21. data/lib/rubocop/rspec/example.rb +1 -25
  22. data/lib/rubocop/rspec/example_group.rb +28 -12
  23. data/lib/rubocop/rspec/hook.rb +49 -0
  24. data/lib/rubocop/rspec/language/node_pattern.rb +1 -0
  25. data/lib/rubocop/rspec/version.rb +1 -1
  26. data/spec/rubocop/cop/rspec/cop_spec.rb +4 -1
  27. data/spec/rubocop/cop/rspec/expect_output_spec.rb +62 -0
  28. data/spec/rubocop/cop/rspec/message_spies_spec.rb +74 -2
  29. data/spec/rubocop/cop/rspec/multiple_expectations_spec.rb +2 -2
  30. data/spec/rubocop/cop/rspec/nested_groups_spec.rb +14 -2
  31. data/spec/rubocop/cop/rspec/repeated_example_spec.rb +65 -0
  32. data/spec/rubocop/cop/rspec/scattered_setup_spec.rb +96 -0
  33. data/spec/rubocop/cop/rspec/single_argument_message_chain_spec.rb +27 -3
  34. data/spec/rubocop/rspec/description_extractor_spec.rb +1 -1
  35. data/spec/rubocop/rspec/example_group_spec.rb +1 -1
  36. data/spec/rubocop/rspec/example_spec.rb +2 -2
  37. data/spec/rubocop/rspec/hook_spec.rb +53 -0
  38. metadata +15 -2
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module RSpec
5
+ # Wrapper for RSpec DSL methods
6
+ class Concept
7
+ include Language, Language::NodePattern
8
+ extend NodePattern::Macros
9
+
10
+ def initialize(node)
11
+ @node = node
12
+ end
13
+
14
+ def eql?(other)
15
+ node.eql?(other.node)
16
+ end
17
+
18
+ alias == eql?
19
+
20
+ def hash
21
+ [self.class, node].hash
22
+ end
23
+
24
+ def to_node
25
+ node
26
+ end
27
+
28
+ protected
29
+
30
+ attr_reader :node
31
+ end
32
+ end
33
+ end
@@ -3,17 +3,11 @@
3
3
  module RuboCop
4
4
  module RSpec
5
5
  # Wrapper for RSpec examples
6
- class Example
7
- extend RuboCop::NodePattern::Macros
8
-
6
+ class Example < Concept
9
7
  def_node_matcher :extract_doc_string, '(send _ _ $str ...)'
10
8
  def_node_matcher :extract_metadata, '(send _ _ _ $...)'
11
9
  def_node_matcher :extract_implementation, '(block send args $_)'
12
10
 
13
- def initialize(node)
14
- @node = node
15
- end
16
-
17
11
  def doc_string
18
12
  extract_doc_string(definition)
19
13
  end
@@ -26,20 +20,6 @@ module RuboCop
26
20
  extract_implementation(node)
27
21
  end
28
22
 
29
- def eql?(other)
30
- node.eql?(other.node)
31
- end
32
-
33
- alias == eql?
34
-
35
- def hash
36
- [self.class, node].hash
37
- end
38
-
39
- def to_node
40
- node
41
- end
42
-
43
23
  def definition
44
24
  if node.send_type?
45
25
  node
@@ -47,10 +27,6 @@ module RuboCop
47
27
  node.children.first
48
28
  end
49
29
  end
50
-
51
- protected
52
-
53
- attr_reader :node
54
30
  end
55
31
  end
56
32
  end
@@ -3,12 +3,7 @@
3
3
  module RuboCop
4
4
  module RSpec
5
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
-
6
+ class ExampleGroup < Concept
12
7
  # @!method scope_change?(node)
13
8
  #
14
9
  # Detect if the node is an example group or shared example
@@ -18,21 +13,42 @@ module RuboCop
18
13
  def_node_matcher :scope_change?,
19
14
  (ExampleGroups::ALL + SharedGroups::ALL).block_pattern
20
15
 
21
- def initialize(node)
22
- @node = node
23
- end
16
+ # @!method hook(node)
17
+ #
18
+ # Detect if node is `before`, `after`, `around`
19
+ def_node_matcher :hook, <<-PATTERN
20
+ (block {$(send nil #{Hooks::ALL.node_pattern_union} ...)} ...)
21
+ PATTERN
24
22
 
25
23
  def examples
26
24
  examples_in_scope(node).map(&Example.public_method(:new))
27
25
  end
28
26
 
27
+ def hooks
28
+ hooks_in_scope(node).map(&Hook.public_method(:new))
29
+ end
30
+
29
31
  private
30
32
 
31
- attr_reader :node
33
+ def hooks_in_scope(node)
34
+ node.each_child_node.flat_map do |child|
35
+ find_hooks(child)
36
+ end
37
+ end
38
+
39
+ def find_hooks(node)
40
+ return [] if scope_change?(node) || example?(node)
41
+
42
+ if hook(node)
43
+ [node]
44
+ else
45
+ hooks_in_scope(node)
46
+ end
47
+ end
32
48
 
33
- def examples_in_scope(node)
49
+ def examples_in_scope(node, &blk)
34
50
  node.each_child_node.flat_map do |child|
35
- find_examples(child)
51
+ find_examples(child, &blk)
36
52
  end
37
53
  end
38
54
 
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module RSpec
5
+ # Wrapper for RSpec hook
6
+ class Hook < Concept
7
+ STANDARDIZED_SCOPES = %i(each context suite).freeze
8
+ private_constant(:STANDARDIZED_SCOPES)
9
+
10
+ def name
11
+ node.method_name
12
+ end
13
+
14
+ def knowable_scope?
15
+ return true unless scope_argument
16
+
17
+ scope_argument.sym_type?
18
+ end
19
+
20
+ def valid_scope?
21
+ STANDARDIZED_SCOPES.include?(scope)
22
+ end
23
+
24
+ def example?
25
+ scope.equal?(:each)
26
+ end
27
+
28
+ def scope
29
+ case scope_name
30
+ when nil, :each, :example then :each
31
+ when :context, :all then :context
32
+ when :suite then :suite
33
+ else
34
+ scope_name
35
+ end
36
+ end
37
+
38
+ private
39
+
40
+ def scope_name
41
+ scope_argument.to_a.first
42
+ end
43
+
44
+ def scope_argument
45
+ node.method_args.first
46
+ end
47
+ end
48
+ end
49
+ end
@@ -8,6 +8,7 @@ module RuboCop
8
8
  extend RuboCop::NodePattern::Macros
9
9
 
10
10
  def_node_matcher :example_group?, ExampleGroups::ALL.block_pattern
11
+ def_node_matcher :example?, Examples::ALL.block_pattern
11
12
  end
12
13
  end
13
14
  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.9.1'.freeze
7
+ STRING = '1.10.0'.freeze
8
8
  end
9
9
  end
10
10
  end
@@ -20,7 +20,9 @@ RSpec.describe RuboCop::Cop::RSpec::Cop do
20
20
  end
21
21
 
22
22
  let(:fake_cop) do
23
- Class.new(described_class) do
23
+ stub_const('RuboCop::RSpec', Module.new)
24
+ # rubocop:disable ClassAndModuleChildren
25
+ class RuboCop::RSpec::FakeCop < described_class
24
26
  def self.name
25
27
  'RuboCop::RSpec::FakeCop'
26
28
  end
@@ -29,6 +31,7 @@ RSpec.describe RuboCop::Cop::RSpec::Cop do
29
31
  add_offense(node, :expression, 'I flag everything')
30
32
  end
31
33
  end
34
+ RuboCop::RSpec::FakeCop
32
35
  end
33
36
 
34
37
  let(:rspec_patterns) { ['_spec.rb$', '(?:^|/)spec/'] }
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ describe RuboCop::Cop::RSpec::ExpectOutput do
6
+ subject(:cop) { described_class.new }
7
+
8
+ it 'registers an offense for overwriting $stdout within an example' do
9
+ expect_violation(<<-RUBY)
10
+ specify do
11
+ $stdout = StringIO.new
12
+ ^^^^^^^ Use `expect { ... }.to output(...).to_stdout` instead of mutating $stdout
13
+ end
14
+ RUBY
15
+ end
16
+
17
+ it 'registers an offense for overwriting $stderr ' \
18
+ 'within an example scoped hook' do
19
+ expect_violation(<<-RUBY)
20
+ before(:each) do
21
+ $stderr = StringIO.new
22
+ ^^^^^^^ Use `expect { ... }.to output(...).to_stderr` instead of mutating $stderr
23
+ end
24
+ RUBY
25
+ end
26
+
27
+ it 'does not register an offense for interacting with $stdout' do
28
+ expect_no_violations(<<-RUBY)
29
+ specify do
30
+ $stdout.puts("hi")
31
+ end
32
+ RUBY
33
+ end
34
+
35
+ it 'does not flag assignments to other global variables' do
36
+ expect_no_violations(<<-RUBY)
37
+ specify do
38
+ $blah = StringIO.new
39
+ end
40
+ RUBY
41
+ end
42
+
43
+ it 'does not flag assignments to $stdout outside of example scope' do
44
+ expect_no_violations(<<-RUBY)
45
+ before(:suite) do
46
+ $stderr = StringIO.new
47
+ end
48
+ RUBY
49
+ end
50
+
51
+ it 'does not flag assignments to $stdout in example_group scope' do
52
+ expect_no_violations(<<-RUBY)
53
+ describe Foo do
54
+ $stderr = StringIO.new
55
+ end
56
+ RUBY
57
+ end
58
+
59
+ it 'does not flag assigns to $stdout when in the root scope' do
60
+ expect_no_violations('$stderr = StringIO.new')
61
+ end
62
+ end
@@ -8,13 +8,49 @@ describe RuboCop::Cop::RSpec::MessageSpies, :config do
8
8
  { 'EnforcedStyle' => 'have_received' }
9
9
  end
10
10
 
11
- it 'flags expect(...).to receive' do
11
+ it 'flags expect(send).to receive' do
12
12
  expect_violation(<<-RUBY)
13
13
  expect(foo).to receive(:bar)
14
14
  ^^^^^^^ Prefer `have_received` for setting message expectations. Setup `foo` as a spy using `allow` or `instance_spy`.
15
15
  RUBY
16
16
  end
17
17
 
18
+ it 'flags expect(lvar).to receive' do
19
+ expect_violation(<<-RUBY)
20
+ foo = baz
21
+ expect(foo).to receive(:bar)
22
+ ^^^^^^^ Prefer `have_received` for setting message expectations. Setup `foo` as a spy using `allow` or `instance_spy`.
23
+ RUBY
24
+ end
25
+
26
+ it 'flags expect(ivar).to receive' do
27
+ expect_violation(<<-RUBY)
28
+ expect(@foo).to receive(:bar)
29
+ ^^^^^^^ Prefer `have_received` for setting message expectations. Setup `@foo` as a spy using `allow` or `instance_spy`.
30
+ RUBY
31
+ end
32
+
33
+ it 'flags expect(const).to receive' do
34
+ expect_violation(<<-RUBY)
35
+ expect(Foo).to receive(:bar)
36
+ ^^^^^^^ Prefer `have_received` for setting message expectations. Setup `Foo` as a spy using `allow` or `instance_spy`.
37
+ RUBY
38
+ end
39
+
40
+ it 'flags expect(...).not_to receive' do
41
+ expect_violation(<<-RUBY)
42
+ expect(foo).not_to receive(:bar)
43
+ ^^^^^^^ Prefer `have_received` for setting message expectations. Setup `foo` as a spy using `allow` or `instance_spy`.
44
+ RUBY
45
+ end
46
+
47
+ it 'flags expect(...).to_not receive' do
48
+ expect_violation(<<-RUBY)
49
+ expect(foo).to_not receive(:bar)
50
+ ^^^^^^^ Prefer `have_received` for setting message expectations. Setup `foo` as a spy using `allow` or `instance_spy`.
51
+ RUBY
52
+ end
53
+
18
54
  it 'flags expect(...).to receive with' do
19
55
  expect_violation(<<-RUBY)
20
56
  expect(foo).to receive(:bar).with(:baz)
@@ -52,13 +88,49 @@ describe RuboCop::Cop::RSpec::MessageSpies, :config do
52
88
  { 'EnforcedStyle' => 'receive' }
53
89
  end
54
90
 
55
- it 'flags expect(...).to have_received' do
91
+ it 'flags expect(send).to have_received' do
56
92
  expect_violation(<<-RUBY)
57
93
  expect(foo).to have_received(:bar)
58
94
  ^^^^^^^^^^^^^ Prefer `receive` for setting message expectations.
59
95
  RUBY
60
96
  end
61
97
 
98
+ it 'flags expect(lvar).to have_received' do
99
+ expect_violation(<<-RUBY)
100
+ foo = baz
101
+ expect(foo).to have_received(:bar)
102
+ ^^^^^^^^^^^^^ Prefer `receive` for setting message expectations.
103
+ RUBY
104
+ end
105
+
106
+ it 'flags expect(ivar).to have_received' do
107
+ expect_violation(<<-RUBY)
108
+ expect(@foo).to have_received(:bar)
109
+ ^^^^^^^^^^^^^ Prefer `receive` for setting message expectations.
110
+ RUBY
111
+ end
112
+
113
+ it 'flags expect(const).to have_received' do
114
+ expect_violation(<<-RUBY)
115
+ expect(Foo).to have_received(:bar)
116
+ ^^^^^^^^^^^^^ Prefer `receive` for setting message expectations.
117
+ RUBY
118
+ end
119
+
120
+ it 'flags expect(...).not_to have_received' do
121
+ expect_violation(<<-RUBY)
122
+ expect(foo).not_to have_received(:bar)
123
+ ^^^^^^^^^^^^^ Prefer `receive` for setting message expectations.
124
+ RUBY
125
+ end
126
+
127
+ it 'flags expect(...).to_not have_received' do
128
+ expect_violation(<<-RUBY)
129
+ expect(foo).to_not have_received(:bar)
130
+ ^^^^^^^^^^^^^ Prefer `receive` for setting message expectations.
131
+ RUBY
132
+ end
133
+
62
134
  it 'flags expect(...).to have_received with' do
63
135
  expect_violation(<<-RUBY)
64
136
  expect(foo).to have_received(:bar).with(:baz)
@@ -10,7 +10,7 @@ describe RuboCop::Cop::RSpec::MultipleExpectations, :config do
10
10
  expect_violation(<<-RUBY)
11
11
  describe Foo do
12
12
  it 'uses expect twice' do
13
- ^^^^^^^^^^^^^^^^^^^^^^ Too many expectations.
13
+ ^^^^^^^^^^^^^^^^^^^^^^ Example has too many expectations [2/1]
14
14
  expect(foo).to eq(bar)
15
15
  expect(baz).to eq(bar)
16
16
  end
@@ -53,7 +53,7 @@ describe RuboCop::Cop::RSpec::MultipleExpectations, :config do
53
53
  expect_violation(<<-RUBY)
54
54
  describe Foo do
55
55
  it 'uses expect three times' do
56
- ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Too many expectations.
56
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Example has too many expectations [3/2]
57
57
  expect(foo).to eq(bar)
58
58
  expect(baz).to eq(bar)
59
59
  expect(qux).to eq(bar)
@@ -33,8 +33,8 @@ describe RuboCop::Cop::RSpec::NestedGroups, :config do
33
33
  RUBY
34
34
  end
35
35
 
36
- context 'when MaxNesting is configured as 2' do
37
- let(:cop_config) { { 'MaxNesting' => '2' } }
36
+ context 'when Max is configured as 2' do
37
+ let(:cop_config) { { 'Max' => '2' } }
38
38
 
39
39
  it 'flags two levels of nesting' do
40
40
  expect_violation(<<-RUBY)
@@ -51,4 +51,16 @@ describe RuboCop::Cop::RSpec::NestedGroups, :config do
51
51
  RUBY
52
52
  end
53
53
  end
54
+
55
+ context 'when configured with MaxNesting' do
56
+ let(:cop_config) { { 'MaxNesting' => '1' } }
57
+
58
+ it 'emits a deprecation warning' do
59
+ expect { inspect_source(cop, 'describe(Foo) { }', 'foo_spec.rb') }
60
+ .to output(
61
+ 'Configuration key `MaxNesting` for RSpec/NestedGroups is ' \
62
+ "deprecated in favor of `Max`. Please use that instead.\n"
63
+ ).to_stderr
64
+ end
65
+ end
54
66
  end