rubocop-rspec 1.25.1 → 1.26.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 (36) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +7 -0
  3. data/README.md +43 -6
  4. data/config/default.yml +5 -0
  5. data/lib/rubocop/cop/rspec/align_left_let_brace.rb +2 -0
  6. data/lib/rubocop/cop/rspec/align_right_let_brace.rb +2 -0
  7. data/lib/rubocop/cop/rspec/capybara/feature_methods.rb +14 -3
  8. data/lib/rubocop/cop/rspec/describe_class.rb +1 -3
  9. data/lib/rubocop/cop/rspec/empty_line_after_final_let.rb +25 -8
  10. data/lib/rubocop/cop/rspec/empty_line_after_subject.rb +1 -1
  11. data/lib/rubocop/cop/rspec/instance_spy.rb +1 -3
  12. data/lib/rubocop/cop/rspec/leading_subject.rb +1 -1
  13. data/lib/rubocop/cop/rspec/let_before_examples.rb +2 -1
  14. data/lib/rubocop/cop/rspec/message_chain.rb +3 -1
  15. data/lib/rubocop/cop/rspec/named_subject.rb +1 -11
  16. data/lib/rubocop/cop/rspec/overwriting_setup.rb +12 -6
  17. data/lib/rubocop/cop/rspec/pending.rb +0 -3
  18. data/lib/rubocop/cop/rspec/receive_counts.rb +86 -0
  19. data/lib/rubocop/cop/rspec/scattered_let.rb +1 -1
  20. data/lib/rubocop/cop/rspec/single_argument_message_chain.rb +1 -1
  21. data/lib/rubocop/cop/rspec_cops.rb +1 -0
  22. data/lib/rubocop/rspec/align_let_brace.rb +1 -1
  23. data/lib/rubocop/rspec/example_group.rb +5 -7
  24. data/lib/rubocop/rspec/language.rb +1 -5
  25. data/lib/rubocop/rspec/version.rb +1 -1
  26. data/rubocop-rspec.gemspec +2 -2
  27. data/spec/rubocop/cop/rspec/align_left_let_brace_spec.rb +4 -0
  28. data/spec/rubocop/cop/rspec/align_right_let_brace_spec.rb +4 -0
  29. data/spec/rubocop/cop/rspec/capybara/feature_methods_spec.rb +9 -0
  30. data/spec/rubocop/cop/rspec/empty_example_group_spec.rb +14 -0
  31. data/spec/rubocop/cop/rspec/empty_line_after_final_let_spec.rb +49 -0
  32. data/spec/rubocop/cop/rspec/empty_line_after_subject_spec.rb +21 -1
  33. data/spec/rubocop/cop/rspec/overwriting_setup_spec.rb +22 -0
  34. data/spec/rubocop/cop/rspec/receive_counts_spec.rb +88 -0
  35. data/spec/rubocop/rspec/language/selector_set_spec.rb +4 -2
  36. metadata +7 -4
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3aa1fc65d7a5b5430ef4c5a88fa0daa56e09d330dd243225f3ef9b6332b076b3
4
- data.tar.gz: 2e56f9dfd5b0b4f11ba040b032bd75ea366a10f15ac7255144971ddfd56027f3
3
+ metadata.gz: 25c78adb48d8a15bb6229d1d8140abcd3f2e455c846288276ad01cedba024b4a
4
+ data.tar.gz: 0e2fbf154570fca1aaf0a32cf7866902424bab10da2854038f5674c8664a2d11
5
5
  SHA512:
6
- metadata.gz: a6066bc29073c219f495824ddce3c3b7d2237d1d06c28605ce536c46b2ec11d0240f05438d7ff8d4a03fde39d484816eb479122617cf411dcf91caba0b2d2cad
7
- data.tar.gz: f1d974b0e62efee91acc48eef293fee4237ebb7ec3eb25eb210808e10432be9a010867818410e99b9986e518d24d237d77709c8acea41a35b3dab225412168d8
6
+ metadata.gz: 7cc9ec95f341241d6c02083673e09ebd861929d846baae085c927a185998da95f573a18f0e7f3b9e7cdaba7f62812d7b80338534742fb2bde205df2fe29e0f3b
7
+ data.tar.gz: 891e9714f685e8a3b1bee0ef80395cc11fe87eccb3226cd1bef201a90379e4424cbc5bd937cc632ba11efb7463f0654d756450e4ba44556e93dde7ef0838ab1a
data/CHANGELOG.md CHANGED
@@ -2,6 +2,13 @@
2
2
 
3
3
  ## Master (Unreleased)
4
4
 
5
+ ## 1.26.0 (2018-06-06)
6
+
7
+ * Fix false positive in `RSpec/EmptyExampleGroup` cop when methods named like a RSpec method are used. ([@Darhazer][])
8
+ * Fix `Capybara/FeatureMethods` not working when there is require before the spec. ([@Darhazer][])
9
+ * Fix `RSpec/EmptyLineAfterFinalLet`: allow a comment to be placed after latest let, requiring empty line after the comment. ([@Darhazer][])
10
+ * Add `RSpec/ReceiveCounts` cop to enforce usage of :once and :twice matchers. ([@Darhazer][])
11
+
5
12
  ## 1.25.1 (2018-04-10)
6
13
 
7
14
  * Fix false positive in `RSpec/Pending` cop when pending is used as a method name. ([@Darhazer][])
data/README.md CHANGED
@@ -2,14 +2,13 @@
2
2
 
3
3
  [![Join the chat at https://gitter.im/rubocop-rspec/Lobby](https://badges.gitter.im/rubocop-rspec/Lobby.svg)](https://gitter.im/rubocop-rspec/Lobby)
4
4
  [![Gem Version](https://badge.fury.io/rb/rubocop-rspec.svg)](https://rubygems.org/gems/rubocop-rspec)
5
- [![CircleCI](https://circleci.com/gh/rubocop-rspec/rubocop-rspec.svg?style=svg)](https://circleci.com/gh/rubocop-rspec/rubocop-rspec)
6
- [![Test Coverage](https://api.codeclimate.com/v1/badges/f6254deb61671e357f30/test_coverage)](https://codeclimate.com/github/rubocop-rspec/rubocop-rspec/test_coverage)
7
- [![Maintainability](https://api.codeclimate.com/v1/badges/f6254deb61671e357f30/maintainability)](https://codeclimate.com/github/rubocop-rspec/rubocop-rspec/maintainability)
5
+ [![CircleCI](https://circleci.com/gh/rubocop-hq/rubocop-rspec.svg?style=svg)](https://circleci.com/gh/rubocop-hq/rubocop-rspec)
6
+ [![Test Coverage](https://api.codeclimate.com/v1/badges/8ffaabf633c968c22bdd/test_coverage)](https://codeclimate.com/github/rubocop-hq/rubocop-rspec/test_coverage)
7
+ [![Maintainability](https://api.codeclimate.com/v1/badges/8ffaabf633c968c22bdd/maintainability)](https://codeclimate.com/github/rubocop-hq/rubocop-rspec/maintainability)
8
8
 
9
9
  RSpec-specific analysis for your projects, as an extension to
10
10
  [RuboCop](https://github.com/bbatsov/rubocop).
11
11
 
12
-
13
12
  ## Installation
14
13
 
15
14
  Just install the `rubocop-rspec` gem
@@ -24,7 +23,6 @@ or if you use bundler put this in your `Gemfile`
24
23
  gem 'rubocop-rspec'
25
24
  ```
26
25
 
27
-
28
26
  ## Usage
29
27
 
30
28
  You need to tell RuboCop to load the RSpec extension. There are three
@@ -98,10 +96,49 @@ RSpec/FilePath:
98
96
  - spec/my_poorly_named_spec_file.rb
99
97
  ```
100
98
 
99
+ ## Non-goals of RuboCop RSpec
100
+
101
+ ### Enforcing `should` vs. `expect` syntax
102
+
103
+ Enforcing
104
+
105
+ ```ruby
106
+ expect(calculator.compute(line_item)).to eq(5)
107
+ ```
108
+
109
+ over
110
+
111
+ ```ruby
112
+ calculator.compute(line_item).should == 5
113
+ ```
114
+
115
+ is a feature of RSpec itself – you can read about it in the [RSpec Documentation](https://relishapp.com/rspec/rspec-expectations/docs/syntax-configuration#disable-should-syntax)
116
+
117
+ ### Enforcing an explicit RSpec receiver for top-level methods (disabling monkey patching)
118
+
119
+ Enforcing
120
+
121
+ ```ruby
122
+ Rspec.describe MyClass do
123
+ ...
124
+ end
125
+ ```
126
+
127
+ over
128
+
129
+ ```ruby
130
+ describe MyClass do
131
+ ...
132
+ end
133
+ ```
134
+
135
+ can be achieved using RSpec's `disable_monkey_patching!` method, which you can read more about in the [RSpec Documentation](https://relishapp.com/rspec/rspec-core/v/3-7/docs/configuration/zero-monkey-patching-mode#monkey-patched-methods-are-undefined-with-%60disable-monkey-patching!%60). This will also prevent `should` from being defined on every object in your system.
136
+
137
+ Before disabling `should` you will need all your specs to use the `expect` syntax. You can use [Transpec](http://yujinakayama.me/transpec/), which will do the conversion for you.
101
138
 
102
139
  ## Contributing
103
140
 
104
- Checkout the [contribution guidelines](.github/CONTRIBUTING.md)
141
+ Checkout the [contribution guidelines](.github/CONTRIBUTING.md).
105
142
 
106
143
  ## License
107
144
 
data/config/default.yml CHANGED
@@ -299,6 +299,11 @@ RSpec/Pending:
299
299
  Description: Checks for any pending or skipped examples.
300
300
  StyleGuide: http://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/Pending
301
301
 
302
+ RSpec/ReceiveCounts:
303
+ Enabled: true
304
+ Description: Check for `once` and `twice` receive counts matchers usage.
305
+ StyleGuide: http://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/ReceiveCounts
306
+
302
307
  RSpec/RepeatedDescription:
303
308
  Enabled: true
304
309
  Description: Check for repeated description strings in example groups.
@@ -25,6 +25,8 @@ module RuboCop
25
25
  end
26
26
 
27
27
  def investigate(_processed_source)
28
+ return if processed_source.blank?
29
+
28
30
  token_aligner.offending_tokens.each do |let|
29
31
  add_offense(let, location: :begin)
30
32
  end
@@ -25,6 +25,8 @@ module RuboCop
25
25
  end
26
26
 
27
27
  def investigate(_processed_source)
28
+ return if processed_source.blank?
29
+
28
30
  token_aligner.offending_tokens.each do |let|
29
31
  add_offense(let, location: :end)
30
32
  end
@@ -66,7 +66,7 @@ module RuboCop
66
66
  PATTERN
67
67
 
68
68
  def on_block(node)
69
- return unless spec?(root_node)
69
+ return unless inside_spec?(node)
70
70
 
71
71
  feature_method(node) do |send_node, match|
72
72
  next if enabled?(match)
@@ -87,8 +87,19 @@ module RuboCop
87
87
 
88
88
  private
89
89
 
90
- def root_node
91
- processed_source.ast
90
+ def inside_spec?(node)
91
+ return spec?(node) if root_node?(node)
92
+
93
+ root = node.ancestors.find { |parent| root_node?(parent) }
94
+ spec?(root)
95
+ end
96
+
97
+ def root_node?(node)
98
+ node.parent.nil? || root_with_siblings?(node.parent)
99
+ end
100
+
101
+ def root_with_siblings?(node)
102
+ node.begin_type? && node.parent.nil?
92
103
  end
93
104
 
94
105
  def enabled?(method_name)
@@ -39,9 +39,7 @@ module RuboCop
39
39
  (sym {:request :feature :system :routing :view}))
40
40
  PATTERN
41
41
 
42
- def_node_matcher :shared_group?, <<-PATTERN
43
- (block (send {(const nil? :RSpec) nil?} #{SharedGroups::ALL.node_pattern_union} ...) ...)
44
- PATTERN
42
+ def_node_matcher :shared_group?, SharedGroups::ALL.block_pattern
45
43
 
46
44
  def on_top_level_describe(node, args)
47
45
  return if shared_group?(root_node)
@@ -17,9 +17,11 @@ module RuboCop
17
17
  #
18
18
  # it { does_something }
19
19
  class EmptyLineAfterFinalLet < Cop
20
+ include RangeHelp
21
+
20
22
  MSG = 'Add an empty line after the last `let` block.'.freeze
21
23
 
22
- def_node_matcher :let?, '(block $(send nil? {:let :let!} ...) args ...)'
24
+ def_node_matcher :let?, Helpers::ALL.block_pattern
23
25
 
24
26
  def on_block(node)
25
27
  return unless example_group_with_body?(node)
@@ -29,24 +31,28 @@ module RuboCop
29
31
  return if latest_let.nil?
30
32
  return if latest_let.equal?(node.body.children.last)
31
33
 
32
- no_new_line_after(latest_let) do
33
- add_offense(latest_let, location: :expression)
34
+ no_new_line_after(latest_let) do |location|
35
+ add_offense(latest_let, location: location)
34
36
  end
35
37
  end
36
38
 
37
39
  def autocorrect(node)
38
- loc = last_node_loc(node)
39
- ->(corrector) { corrector.insert_after(loc.end, "\n") }
40
+ lambda do |corrector|
41
+ no_new_line_after(node) do |location|
42
+ corrector.insert_after(location.end, "\n")
43
+ end
44
+ end
40
45
  end
41
46
 
42
47
  private
43
48
 
44
49
  def no_new_line_after(node)
45
50
  loc = last_node_loc(node)
51
+ line = loc.line
52
+ line += 1 while comment_line?(processed_source[line])
46
53
 
47
- next_line = processed_source[loc.line]
48
-
49
- yield unless next_line.blank?
54
+ return if processed_source[line].blank?
55
+ yield offending_loc(node, line)
50
56
  end
51
57
 
52
58
  def last_node_loc(node)
@@ -62,6 +68,17 @@ module RuboCop
62
68
 
63
69
  node.each_child_node { |child| heredoc_line(child, &block) }
64
70
  end
71
+
72
+ def offending_loc(node, last_line)
73
+ offending_line = processed_source[last_line - 1]
74
+ if comment_line?(offending_line)
75
+ start = offending_line.index('#')
76
+ length = offending_line.length - start
77
+ source_range(processed_source.buffer, last_line, start, length)
78
+ else
79
+ node.loc.expression
80
+ end
81
+ end
65
82
  end
66
83
  end
67
84
  end
@@ -17,7 +17,7 @@ module RuboCop
17
17
  class EmptyLineAfterSubject < Cop
18
18
  MSG = 'Add empty line after `subject`.'.freeze
19
19
 
20
- def_node_matcher :subject?, '(block $(send nil? :subject ...) args ...)'
20
+ def_node_matcher :subject?, Subject::ALL.block_pattern
21
21
 
22
22
  def on_block(node)
23
23
  return unless subject?(node) && !in_spec_block?(node)
@@ -22,9 +22,7 @@ module RuboCop
22
22
  MSG = 'Use `instance_spy` when you check your double '\
23
23
  'with `have_received`.'.freeze
24
24
 
25
- EXAMPLES = Examples::ALL.node_pattern_union.freeze
26
-
27
- def_node_matcher :example?, "(block $(send nil? #{EXAMPLES}) ...)"
25
+ def_node_matcher :example?, Examples::ALL.block_pattern
28
26
 
29
27
  def_node_search :null_double, <<-PATTERN
30
28
  (lvasgn $_
@@ -31,7 +31,7 @@ module RuboCop
31
31
 
32
32
  MSG = 'Declare `subject` above any other `let` declarations.'.freeze
33
33
 
34
- def_node_matcher :subject?, '(block $(send nil? :subject ...) args ...)'
34
+ def_node_matcher :subject?, Subject::ALL.block_pattern
35
35
 
36
36
  def on_block(node)
37
37
  return unless subject?(node) && !in_spec_block?(node)
@@ -35,7 +35,8 @@ module RuboCop
35
35
 
36
36
  MSG = 'Move `let` before the examples in the group.'.freeze
37
37
 
38
- def_node_matcher :let?, '(block (send nil? {:let :let!} ...) ...)'
38
+ def_node_matcher :let?, Helpers::ALL.block_pattern
39
+
39
40
  def_node_matcher :example_or_group?, <<-PATTERN
40
41
  {
41
42
  #{(Examples::ALL + ExampleGroups::ALL).block_pattern}
@@ -14,7 +14,9 @@ module RuboCop
14
14
  class MessageChain < Cop
15
15
  MSG = 'Avoid stubbing using `%<method>s`.'.freeze
16
16
 
17
- def_node_matcher :message_chain, Matchers::MESSAGE_CHAIN.send_pattern
17
+ def_node_matcher :message_chain, <<-PATTERN
18
+ (send _ {:receive_message_chain :stub_chain} ...)
19
+ PATTERN
18
20
 
19
21
  def on_send(node)
20
22
  message_chain(node) { add_offense(node, location: :selector) }
@@ -48,7 +48,7 @@ module RuboCop
48
48
  }
49
49
  PATTERN
50
50
 
51
- def_node_matcher :unnamed_subject, '$(send nil? :subject)'
51
+ def_node_search :subject_usage, '$(send nil? :subject)'
52
52
 
53
53
  def on_block(node)
54
54
  return unless rspec_block?(node)
@@ -57,16 +57,6 @@ module RuboCop
57
57
  add_offense(subject_node, location: :selector)
58
58
  end
59
59
  end
60
-
61
- private
62
-
63
- def subject_usage(node, &block)
64
- unnamed_subject(node, &block)
65
-
66
- node.each_child_node do |child|
67
- subject_usage(child, &block)
68
- end
69
- end
70
60
  end
71
61
  end
72
62
  end
@@ -22,11 +22,11 @@ module RuboCop
22
22
  # let(:baz) { baz }
23
23
  # let!(:other) { other }
24
24
  class OverwritingSetup < Cop
25
+ include RuboCop::RSpec::Util
26
+
25
27
  MSG = '`%<name>s` is already defined.'.freeze
26
28
 
27
- def_node_matcher :setup?, <<-PATTERN
28
- (block (send nil? {:let :let! :subject} (sym $_)) ...)
29
- PATTERN
29
+ def_node_matcher :setup?, (Helpers::ALL + Subject::ALL).block_pattern
30
30
 
31
31
  def on_block(node)
32
32
  return unless example_group_with_body?(node)
@@ -45,9 +45,15 @@ module RuboCop
45
45
  def find_duplicates(node)
46
46
  setup_expressions = Set.new
47
47
  node.each_child_node do |child|
48
- setup?(child) do |name|
49
- yield child, name unless setup_expressions.add?(name)
50
- end
48
+ next unless setup?(child)
49
+
50
+ name = if child.send_node.arguments?
51
+ child.send_node.first_argument.value
52
+ else
53
+ :subject
54
+ end
55
+
56
+ yield child, name unless setup_expressions.add?(name)
51
57
  end
52
58
  end
53
59
  end
@@ -43,11 +43,8 @@ module RuboCop
43
43
 
44
44
  def_node_matcher :pending_block?, PENDING_EXAMPLES.send_pattern
45
45
 
46
- def_node_matcher :rspec?, '(send {(const nil? :RSpec) nil?} ...)'
47
-
48
46
  def on_send(node)
49
47
  return unless pending_block?(node) || skipped_from_metadata?(node)
50
- return unless rspec?(node)
51
48
 
52
49
  add_offense(node, location: :expression)
53
50
  end
@@ -0,0 +1,86 @@
1
+ module RuboCop
2
+ module Cop
3
+ module RSpec
4
+ # Check for `once` and `twice` receive counts matchers usage.
5
+ #
6
+ # @example
7
+ #
8
+ # # bad
9
+ # expect(foo).to receive(:bar).exactly(1).times
10
+ # expect(foo).to receive(:bar).exactly(2).times
11
+ # expect(foo).to receive(:bar).at_least(1).times
12
+ # expect(foo).to receive(:bar).at_least(2).times
13
+ # expect(foo).to receive(:bar).at_most(1).times
14
+ # expect(foo).to receive(:bar).at_most(2).times
15
+ #
16
+ # # good
17
+ # expect(foo).to receive(:bar).once
18
+ # expect(foo).to receive(:bar).twice
19
+ # expect(foo).to receive(:bar).at_least(:once)
20
+ # expect(foo).to receive(:bar).at_least(:twice)
21
+ # expect(foo).to receive(:bar).at_most(:once)
22
+ # expect(foo).to receive(:bar).at_most(:twice).times
23
+ #
24
+ class ReceiveCounts < Cop
25
+ include RangeHelp
26
+
27
+ MSG = 'Use `%<alternative>s` instead of `%<original>s`.'.freeze
28
+
29
+ def_node_matcher :receive_counts, <<-PATTERN
30
+ (send $(send _ {:exactly :at_least :at_most} (int {1 2})) :times)
31
+ PATTERN
32
+
33
+ def on_send(node)
34
+ receive_counts(node) do |offending_node|
35
+ offending_range = range(node, offending_node)
36
+
37
+ add_offense(
38
+ offending_node,
39
+ message: message_for(offending_node, offending_range.source),
40
+ location: offending_range
41
+ )
42
+ end
43
+ end
44
+
45
+ def autocorrect(node)
46
+ lambda do |corrector|
47
+ replacement = matcher_for(
48
+ node.method_name,
49
+ node.first_argument.source.to_i
50
+ )
51
+ corrector.replace(
52
+ range(node.parent, node),
53
+ replacement
54
+ )
55
+ end
56
+ end
57
+
58
+ private
59
+
60
+ def message_for(node, source)
61
+ alternative = matcher_for(
62
+ node.method_name,
63
+ node.first_argument.source.to_i
64
+ )
65
+ format(MSG, alternative: alternative, original: source)
66
+ end
67
+
68
+ def matcher_for(method, count)
69
+ matcher = count == 1 ? 'once' : 'twice'
70
+ if method == :exactly
71
+ ".#{matcher}"
72
+ else
73
+ ".#{method}(:#{matcher})"
74
+ end
75
+ end
76
+
77
+ def range(node, offending_node)
78
+ range_between(
79
+ offending_node.loc.selector.begin_pos - 1, # match the dot as well
80
+ node.loc.expression.end_pos
81
+ )
82
+ end
83
+ end
84
+ end
85
+ end
86
+ end
@@ -29,7 +29,7 @@ module RuboCop
29
29
  class ScatteredLet < Cop
30
30
  MSG = 'Group all let/let! blocks in the example group together.'.freeze
31
31
 
32
- def_node_matcher :let?, '(block (send nil? {:let :let!} ...) ...)'
32
+ def_node_matcher :let?, Helpers::ALL.block_pattern
33
33
 
34
34
  def on_block(node)
35
35
  return unless example_group_with_body?(node)
@@ -19,7 +19,7 @@ module RuboCop
19
19
  '`%<called>s` with a single argument.'.freeze
20
20
 
21
21
  def_node_matcher :message_chain, <<-PATTERN
22
- (send _ #{Matchers::MESSAGE_CHAIN.node_pattern_union} $_)
22
+ (send _ {:receive_message_chain :stub_chain} $_)
23
23
  PATTERN
24
24
 
25
25
  def_node_matcher :single_key_hash?, '(hash pair)'
@@ -57,6 +57,7 @@ require_relative 'rspec/not_to_not'
57
57
  require_relative 'rspec/overwriting_setup'
58
58
  require_relative 'rspec/pending'
59
59
  require_relative 'rspec/predicate_matcher'
60
+ require_relative 'rspec/receive_counts'
60
61
  require_relative 'rspec/repeated_description'
61
62
  require_relative 'rspec/repeated_example'
62
63
  require_relative 'rspec/return_from_stub'
@@ -6,7 +6,7 @@ module RuboCop
6
6
  class AlignLetBrace
7
7
  extend NodePattern::Macros
8
8
 
9
- def_node_matcher :let?, Language::Helpers::ALL.block_pattern.freeze
9
+ def_node_matcher :let?, Language::Helpers::ALL.block_pattern
10
10
 
11
11
  def initialize(root, token)
12
12
  @root = root
@@ -14,14 +14,12 @@ module RuboCop
14
14
  ExampleGroups::ALL + SharedGroups::ALL + Includes::ALL
15
15
  ).block_pattern
16
16
 
17
- # @!method hook(node)
17
+ # @!method hook?(node)
18
18
  #
19
19
  # Detect if node is `before`, `after`, `around`
20
- def_node_matcher :hook, <<-PATTERN
21
- (block {$(send nil? #{Hooks::ALL.node_pattern_union} ...)} ...)
22
- PATTERN
20
+ def_node_matcher :hook?, Hooks::ALL.block_pattern
23
21
 
24
- def_node_matcher :subject, Subject::ALL.block_pattern
22
+ def_node_matcher :subject?, Subject::ALL.block_pattern
25
23
 
26
24
  def subjects
27
25
  subjects_in_scope(node)
@@ -46,7 +44,7 @@ module RuboCop
46
44
  def find_subjects(node)
47
45
  return [] if scope_change?(node)
48
46
 
49
- if subject(node)
47
+ if subject?(node)
50
48
  [node]
51
49
  else
52
50
  subjects_in_scope(node)
@@ -62,7 +60,7 @@ module RuboCop
62
60
  def find_hooks(node)
63
61
  return [] if scope_change?(node) || example?(node)
64
62
 
65
- if hook(node)
63
+ if hook?(node)
66
64
  [node]
67
65
  else
68
66
  hooks_in_scope(node)
@@ -27,7 +27,7 @@ module RuboCop
27
27
  end
28
28
 
29
29
  def send_pattern
30
- "(send _ #{node_pattern_union} ...)"
30
+ "(send {(const nil? :RSpec) nil?} #{node_pattern_union} ...)"
31
31
  end
32
32
 
33
33
  def node_pattern_union
@@ -43,10 +43,6 @@ module RuboCop
43
43
  attr_reader :selectors
44
44
  end
45
45
 
46
- module Matchers
47
- MESSAGE_CHAIN = SelectorSet.new(%i[receive_message_chain stub_chain])
48
- end
49
-
50
46
  module ExampleGroups
51
47
  GROUPS = SelectorSet.new(%i[describe context feature example_group])
52
48
  SKIPPED = SelectorSet.new(%i[xdescribe xcontext xfeature])
@@ -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.25.1'.freeze
7
+ STRING = '1.26.0'.freeze
8
8
  end
9
9
  end
10
10
  end
@@ -8,7 +8,7 @@ Gem::Specification.new do |spec|
8
8
  Code style checking for RSpec files.
9
9
  A plugin for the RuboCop code style enforcing & linting tool.
10
10
  DESCRIPTION
11
- spec.homepage = 'https://github.com/rubocop-rspec/rubocop-rspec'
11
+ spec.homepage = 'https://github.com/rubocop-hq/rubocop-rspec'
12
12
  spec.authors = ['John Backus', 'Ian MacLeod', 'Nils Gemeinhardt']
13
13
  spec.email = [
14
14
  'johncbackus@gmail.com',
@@ -33,7 +33,7 @@ Gem::Specification.new do |spec|
33
33
  spec.extra_rdoc_files = ['MIT-LICENSE.md', 'README.md']
34
34
 
35
35
  spec.metadata = {
36
- 'changelog_uri' => 'https://github.com/rubocop-rspec/rubocop-rspec/blob/master/CHANGELOG.md',
36
+ 'changelog_uri' => 'https://github.com/rubocop-hq/rubocop-rspec/blob/master/CHANGELOG.md',
37
37
  'documentation_uri' => 'https://rubocop-rspec.readthedocs.io/'
38
38
  }
39
39
 
@@ -24,6 +24,10 @@ RSpec.describe RuboCop::Cop::RSpec::AlignLeftLetBrace do
24
24
  RUBY
25
25
  end
26
26
 
27
+ it 'works with empty file' do
28
+ expect_no_offenses('')
29
+ end
30
+
27
31
  offensive_source = <<-RUBY
28
32
  let(:foo) { bar }
29
33
  let(:hi) { baz }
@@ -24,6 +24,10 @@ RSpec.describe RuboCop::Cop::RSpec::AlignRightLetBrace do
24
24
  RUBY
25
25
  end
26
26
 
27
+ it 'works with empty file' do
28
+ expect_no_offenses('')
29
+ end
30
+
27
31
  offensive_source = <<-RUBY
28
32
  let(:foo) { a }
29
33
  let(:hi) { ab }
@@ -76,6 +76,15 @@ RSpec.describe RuboCop::Cop::RSpec::Capybara::FeatureMethods, :config do
76
76
  RUBY
77
77
  end
78
78
 
79
+ it 'allows includes before the spec' do
80
+ expect_offense(<<-RUBY)
81
+ require 'rails_helper'
82
+
83
+ RSpec.feature 'Foo' do; end
84
+ ^^^^^^^ Use `describe` instead of `feature`.
85
+ RUBY
86
+ end
87
+
79
88
  context 'with configured `EnabledMethods`' do
80
89
  let(:cop_config) { { 'EnabledMethods' => %w[feature] } }
81
90
 
@@ -49,6 +49,20 @@ RSpec.describe RuboCop::Cop::RSpec::EmptyExampleGroup, :config do
49
49
  RUBY
50
50
  end
51
51
 
52
+ it 'does not flag methods matching example group names' do
53
+ expect_no_offenses(<<-RUBY)
54
+ describe Foo do
55
+ it 'yields a block when given' do
56
+ value = nil
57
+
58
+ helper.feature('whatevs') { value = 5 }
59
+
60
+ expect(value).to be 5
61
+ end
62
+ end
63
+ RUBY
64
+ end
65
+
52
66
  it 'does not recognize custom include methods by default' do
53
67
  expect_offense(<<-RUBY)
54
68
  describe Foo do
@@ -38,6 +38,30 @@ RSpec.describe RuboCop::Cop::RSpec::EmptyLineAfterFinalLet do
38
38
  RUBY
39
39
  end
40
40
 
41
+ it 'allows comment followed by an empty line after let' do
42
+ expect_no_offenses(<<-RUBY)
43
+ RSpec.describe User do
44
+ let(:a) { a }
45
+ let(:b) { b }
46
+ # end of setup
47
+
48
+ it { expect(a).to eq(b) }
49
+ end
50
+ RUBY
51
+ end
52
+
53
+ it 'flags missing empty line after the comment that comes after last let' do
54
+ expect_offense(<<-RUBY)
55
+ RSpec.describe User do
56
+ let(:a) { a }
57
+ let(:b) { b }
58
+ # end of setup
59
+ ^^^^^^^^^^^^^^ Add an empty line after the last `let` block.
60
+ it { expect(a).to eq(b) }
61
+ end
62
+ RUBY
63
+ end
64
+
41
65
  it 'ignores empty lines between the lets' do
42
66
  expect_offense(<<-RUBY)
43
67
  RSpec.describe User do
@@ -136,6 +160,31 @@ RSpec.describe RuboCop::Cop::RSpec::EmptyLineAfterFinalLet do
136
160
  end
137
161
  RUBY
138
162
 
163
+ include_examples 'autocorrect',
164
+ bad_example,
165
+ good_example
166
+
167
+ bad_example = <<-RUBY
168
+ RSpec.describe User do
169
+ let(:params) { foo }
170
+ # a multiline comment marking
171
+ # the end of setup
172
+ it 'has a new line' do
173
+ end
174
+ end
175
+ RUBY
176
+
177
+ good_example = <<-RUBY
178
+ RSpec.describe User do
179
+ let(:params) { foo }
180
+ # a multiline comment marking
181
+ # the end of setup
182
+
183
+ it 'has a new line' do
184
+ end
185
+ end
186
+ RUBY
187
+
139
188
  include_examples 'autocorrect',
140
189
  bad_example,
141
190
  good_example
@@ -13,6 +13,16 @@ RSpec.describe RuboCop::Cop::RSpec::EmptyLineAfterSubject do
13
13
  RUBY
14
14
  end
15
15
 
16
+ it 'checks for empty line after subject!' do
17
+ expect_offense(<<-RUBY)
18
+ RSpec.describe User do
19
+ subject! { described_class.new }
20
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Add empty line after `subject`.
21
+ let(:params) { foo }
22
+ end
23
+ RUBY
24
+ end
25
+
16
26
  it 'approves empty line after subject' do
17
27
  expect_no_offenses(<<-RUBY)
18
28
  RSpec.describe User do
@@ -23,6 +33,16 @@ RSpec.describe RuboCop::Cop::RSpec::EmptyLineAfterSubject do
23
33
  RUBY
24
34
  end
25
35
 
36
+ it 'approves empty line after subject!' do
37
+ expect_no_offenses(<<-RUBY)
38
+ RSpec.describe User do
39
+ subject! { described_class.new }
40
+
41
+ let(:params) { foo }
42
+ end
43
+ RUBY
44
+ end
45
+
26
46
  it 'handles subjects in tests' do
27
47
  expect_no_offenses(<<-RUBY)
28
48
  RSpec.describe User do
@@ -48,7 +68,7 @@ RSpec.describe RuboCop::Cop::RSpec::EmptyLineAfterSubject do
48
68
  RUBY
49
69
  end
50
70
 
51
- it 'handles let being the latest node' do
71
+ it 'handles subject being the latest node' do
52
72
  expect_no_offenses(<<-RUBY)
53
73
  RSpec.describe User do
54
74
  subject { described_user }
@@ -22,6 +22,17 @@ RSpec.describe RuboCop::Cop::RSpec::OverwritingSetup do
22
22
  RUBY
23
23
  end
24
24
 
25
+ it 'works with `subject!` and `let!`' do
26
+ expect_offense(<<-RUBY)
27
+ RSpec.describe User do
28
+ subject!(:a) { a }
29
+
30
+ let!(:a) { b }
31
+ ^^^^^^^^^^^^^^ `a` is already defined.
32
+ end
33
+ RUBY
34
+ end
35
+
25
36
  it 'finds `let!` overwriting `let`' do
26
37
  expect_offense(<<-RUBY)
27
38
  RSpec.describe User do
@@ -44,6 +55,17 @@ RSpec.describe RuboCop::Cop::RSpec::OverwritingSetup do
44
55
  RUBY
45
56
  end
46
57
 
58
+ it 'handles unnamed subjects' do
59
+ expect_offense(<<-RUBY)
60
+ RSpec.describe User do
61
+ subject { a }
62
+
63
+ let(:subject) { b }
64
+ ^^^^^^^^^^^^^^^^^^^ `subject` is already defined.
65
+ end
66
+ RUBY
67
+ end
68
+
47
69
  it 'does not encounter an error when handling an empty describe' do
48
70
  expect { inspect_source('RSpec.describe(User) do end', 'a_spec.rb') }
49
71
  .not_to raise_error
@@ -0,0 +1,88 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe RuboCop::Cop::RSpec::ReceiveCounts do
4
+ subject(:cop) { described_class.new }
5
+
6
+ it 'flags usage of `exactly(1).times`' do
7
+ expect_offense(<<-RUBY)
8
+ expect(foo).to receive(:bar).exactly(1).times
9
+ ^^^^^^^^^^^^^^^^^ Use `.once` instead of `.exactly(1).times`.
10
+ RUBY
11
+ end
12
+
13
+ it 'flags usage of `exactly(2).times`' do
14
+ expect_offense(<<-RUBY)
15
+ expect(foo).to receive(:bar).exactly(2).times
16
+ ^^^^^^^^^^^^^^^^^ Use `.twice` instead of `.exactly(2).times`.
17
+ RUBY
18
+ end
19
+
20
+ it 'allows `exactly(3).times`' do
21
+ expect_no_offenses(<<-RUBY)
22
+ expect(foo).to receive(:bar).exactly(3).times
23
+ RUBY
24
+ end
25
+
26
+ it 'allows `exactly(n).times`' do
27
+ expect_no_offenses(<<-RUBY)
28
+ expect(foo).to receive(:bar).exactly(n).times
29
+ RUBY
30
+ end
31
+
32
+ it 'flags usage of `exactly(1).times` after `with`' do
33
+ expect_offense(<<-RUBY)
34
+ expect(foo).to receive(:bar).with(baz).exactly(1).times
35
+ ^^^^^^^^^^^^^^^^^ Use `.once` instead of `.exactly(1).times`.
36
+ RUBY
37
+ end
38
+
39
+ it 'flags usage of `exactly(1).times` with return value' do
40
+ expect_offense(<<-RUBY)
41
+ expect(foo).to receive(:bar).exactly(1).times.and_return(true)
42
+ ^^^^^^^^^^^^^^^^^ Use `.once` instead of `.exactly(1).times`.
43
+ RUBY
44
+ end
45
+
46
+ it 'flags usage of `exactly(1).times` with a block' do
47
+ expect_offense(<<-RUBY)
48
+ expect(foo).to receive(:bar).exactly(1).times { true }
49
+ ^^^^^^^^^^^^^^^^^ Use `.once` instead of `.exactly(1).times`.
50
+ RUBY
51
+ end
52
+
53
+ it 'flags usage of `at_least(1).times`' do
54
+ expect_offense(<<-RUBY)
55
+ expect(foo).to receive(:bar).at_least(1).times
56
+ ^^^^^^^^^^^^^^^^^^ Use `.at_least(:once)` instead of `.at_least(1).times`.
57
+ RUBY
58
+ end
59
+
60
+ it 'flags usage of `at_least(2).times`' do
61
+ expect_offense(<<-RUBY)
62
+ expect(foo).to receive(:bar).at_least(2).times
63
+ ^^^^^^^^^^^^^^^^^^ Use `.at_least(:twice)` instead of `.at_least(2).times`.
64
+ RUBY
65
+ end
66
+
67
+ it 'flags usage of `at_most(1).times`' do
68
+ expect_offense(<<-RUBY)
69
+ expect(foo).to receive(:bar).at_most(1).times
70
+ ^^^^^^^^^^^^^^^^^ Use `.at_most(:once)` instead of `.at_most(1).times`.
71
+ RUBY
72
+ end
73
+
74
+ it 'flags usage of `at_most(2).times`' do
75
+ expect_offense(<<-RUBY)
76
+ expect(foo).to receive(:bar).at_most(2).times
77
+ ^^^^^^^^^^^^^^^^^ Use `.at_most(:twice)` instead of `.at_most(2).times`.
78
+ RUBY
79
+ end
80
+
81
+ include_examples 'autocorrect',
82
+ 'expect(foo).to receive(:bar).exactly(1).times { true }',
83
+ 'expect(foo).to receive(:bar).once { true }'
84
+
85
+ include_examples 'autocorrect',
86
+ 'expect(foo).to receive(:bar).at_least(2).times { true }',
87
+ 'expect(foo).to receive(:bar).at_least(:twice) { true }'
88
+ end
@@ -35,14 +35,16 @@ RSpec.describe RuboCop::RSpec::Language::SelectorSet do
35
35
 
36
36
  describe '#send_pattern' do
37
37
  it 'builds a send matching pattern' do
38
- expect(selector_set.send_pattern).to eql('(send _ {:foo :bar} ...)')
38
+ expect(selector_set.send_pattern).to eql(
39
+ '(send {(const nil? :RSpec) nil?} {:foo :bar} ...)'
40
+ )
39
41
  end
40
42
  end
41
43
 
42
44
  describe '#block_pattern' do
43
45
  it 'builds a block matching pattern' do
44
46
  expect(selector_set.block_pattern).to eql(
45
- '(block (send _ {:foo :bar} ...) ...)'
47
+ '(block (send {(const nil? :RSpec) nil?} {:foo :bar} ...) ...)'
46
48
  )
47
49
  end
48
50
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rubocop-rspec
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.25.1
4
+ version: 1.26.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - John Backus
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2018-04-10 00:00:00.000000000 Z
13
+ date: 2018-06-06 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: rubocop
@@ -169,6 +169,7 @@ files:
169
169
  - lib/rubocop/cop/rspec/pending.rb
170
170
  - lib/rubocop/cop/rspec/predicate_matcher.rb
171
171
  - lib/rubocop/cop/rspec/rails/http_status.rb
172
+ - lib/rubocop/cop/rspec/receive_counts.rb
172
173
  - lib/rubocop/cop/rspec/repeated_description.rb
173
174
  - lib/rubocop/cop/rspec/repeated_example.rb
174
175
  - lib/rubocop/cop/rspec/return_from_stub.rb
@@ -256,6 +257,7 @@ files:
256
257
  - spec/rubocop/cop/rspec/pending_spec.rb
257
258
  - spec/rubocop/cop/rspec/predicate_matcher_spec.rb
258
259
  - spec/rubocop/cop/rspec/rails/http_status_spec.rb
260
+ - spec/rubocop/cop/rspec/receive_counts_spec.rb
259
261
  - spec/rubocop/cop/rspec/repeated_description_spec.rb
260
262
  - spec/rubocop/cop/rspec/repeated_example_spec.rb
261
263
  - spec/rubocop/cop/rspec/return_from_stub_spec.rb
@@ -279,11 +281,11 @@ files:
279
281
  - spec/shared/detects_style_behavior.rb
280
282
  - spec/spec_helper.rb
281
283
  - spec/support/expect_offense.rb
282
- homepage: https://github.com/rubocop-rspec/rubocop-rspec
284
+ homepage: https://github.com/rubocop-hq/rubocop-rspec
283
285
  licenses:
284
286
  - MIT
285
287
  metadata:
286
- changelog_uri: https://github.com/rubocop-rspec/rubocop-rspec/blob/master/CHANGELOG.md
288
+ changelog_uri: https://github.com/rubocop-hq/rubocop-rspec/blob/master/CHANGELOG.md
287
289
  documentation_uri: https://rubocop-rspec.readthedocs.io/
288
290
  post_install_message:
289
291
  rdoc_options: []
@@ -362,6 +364,7 @@ test_files:
362
364
  - spec/rubocop/cop/rspec/pending_spec.rb
363
365
  - spec/rubocop/cop/rspec/predicate_matcher_spec.rb
364
366
  - spec/rubocop/cop/rspec/rails/http_status_spec.rb
367
+ - spec/rubocop/cop/rspec/receive_counts_spec.rb
365
368
  - spec/rubocop/cop/rspec/repeated_description_spec.rb
366
369
  - spec/rubocop/cop/rspec/repeated_example_spec.rb
367
370
  - spec/rubocop/cop/rspec/return_from_stub_spec.rb