rubocop-rspec 1.27.0 → 1.28.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 (54) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +11 -0
  3. data/README.md +1 -1
  4. data/config/default.yml +15 -10
  5. data/lib/rubocop/cop/rspec/describe_method.rb +1 -1
  6. data/lib/rubocop/cop/rspec/empty_example_group.rb +1 -1
  7. data/lib/rubocop/cop/rspec/empty_line_after_example_group.rb +2 -4
  8. data/lib/rubocop/cop/rspec/empty_line_after_final_let.rb +1 -3
  9. data/lib/rubocop/cop/rspec/empty_line_after_hook.rb +1 -3
  10. data/lib/rubocop/cop/rspec/empty_line_after_subject.rb +1 -3
  11. data/lib/rubocop/cop/rspec/example_without_description.rb +3 -4
  12. data/lib/rubocop/cop/rspec/expect_in_hook.rb +8 -23
  13. data/lib/rubocop/cop/rspec/expect_output.rb +0 -2
  14. data/lib/rubocop/cop/rspec/factory_bot/attribute_defined_statically.rb +146 -0
  15. data/lib/rubocop/cop/rspec/instance_spy.rb +0 -2
  16. data/lib/rubocop/cop/rspec/iterated_expectation.rb +1 -1
  17. data/lib/rubocop/cop/rspec/leading_subject.rb +1 -6
  18. data/lib/rubocop/cop/rspec/let_before_examples.rb +0 -2
  19. data/lib/rubocop/cop/rspec/missing_example_group_argument.rb +35 -0
  20. data/lib/rubocop/cop/rspec/multiple_expectations.rb +1 -1
  21. data/lib/rubocop/cop/rspec/multiple_subjects.rb +4 -4
  22. data/lib/rubocop/cop/rspec/nested_groups.rb +1 -1
  23. data/lib/rubocop/cop/rspec/pending.rb +1 -1
  24. data/lib/rubocop/cop/rspec/receive_never.rb +43 -0
  25. data/lib/rubocop/cop/rspec/scattered_let.rb +0 -2
  26. data/lib/rubocop/cop/rspec/shared_context.rb +3 -3
  27. data/lib/rubocop/cop/rspec/void_expect.rb +1 -1
  28. data/lib/rubocop/cop/rspec_cops.rb +4 -3
  29. data/lib/rubocop/rspec/align_let_brace.rb +1 -3
  30. data/lib/rubocop/rspec/blank_line_separation.rb +6 -0
  31. data/lib/rubocop/rspec/example_group.rb +0 -7
  32. data/lib/rubocop/rspec/language/node_pattern.rb +6 -0
  33. data/lib/rubocop/rspec/version.rb +1 -1
  34. data/rubocop-rspec.gemspec +2 -2
  35. data/spec/project/project_requires_spec.rb +13 -3
  36. data/spec/rubocop/cop/rspec/before_after_all_spec.rb +2 -2
  37. data/spec/rubocop/cop/rspec/empty_line_after_example_group_spec.rb +15 -0
  38. data/spec/rubocop/cop/rspec/factory_bot/attribute_defined_statically_spec.rb +156 -0
  39. data/spec/rubocop/cop/rspec/let_before_examples_spec.rb +0 -5
  40. data/spec/rubocop/cop/rspec/missing_example_group_argument_spec.rb +55 -0
  41. data/spec/rubocop/cop/rspec/overwriting_setup_spec.rb +0 -5
  42. data/spec/rubocop/cop/rspec/receive_never_spec.rb +45 -0
  43. data/spec/rubocop/cop/rspec/scattered_let_spec.rb +0 -5
  44. data/spec/shared/smoke_test_examples.rb +25 -0
  45. data/spec/smoke_tests/empty_spec.rb +0 -0
  46. data/spec/smoke_tests/factory_bot_spec.rb +11 -0
  47. data/spec/smoke_tests/no_tests_spec.rb +4 -0
  48. data/spec/smoke_tests/weird_rspec_spec.rb +233 -0
  49. data/spec/spec_helper.rb +4 -0
  50. metadata +24 -11
  51. data/lib/rubocop/cop/rspec/factory_bot/dynamic_attribute_defined_statically.rb +0 -93
  52. data/lib/rubocop/cop/rspec/factory_bot/static_attribute_defined_dynamically.rb +0 -81
  53. data/spec/rubocop/cop/rspec/factory_bot/dynamic_attribute_defined_statically_spec.rb +0 -139
  54. data/spec/rubocop/cop/rspec/factory_bot/static_attribute_defined_dynamically_spec.rb +0 -107
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module RSpec
6
+ # Checks that the first argument to an example group is not empty.
7
+ #
8
+ # @example
9
+ # # bad
10
+ # describe do
11
+ # end
12
+ #
13
+ # RSpec.describe do
14
+ # end
15
+ #
16
+ # # good
17
+ # describe TestedClass do
18
+ # end
19
+ #
20
+ # describe "A feature example" do
21
+ # end
22
+ class MissingExampleGroupArgument < Cop
23
+ MSG = 'The first argument to `%<method>s` should not be empty.'.freeze
24
+
25
+ def on_block(node)
26
+ return unless example_group?(node)
27
+ return if node.send_node.arguments?
28
+
29
+ add_offense(node, location: :expression,
30
+ message: format(MSG, method: node.method_name))
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -77,7 +77,7 @@ module RuboCop
77
77
  private
78
78
 
79
79
  def example_with_aggregated_failures?(node)
80
- example = node.children.first
80
+ example = node.send_node
81
81
 
82
82
  (aggregated_failures_by_default? ||
83
83
  with_aggregated_failures?(example)) &&
@@ -36,10 +36,6 @@ module RuboCop
36
36
  class MultipleSubjects < Cop
37
37
  MSG = 'Do not set more than one subject per example group'.freeze
38
38
 
39
- def_node_matcher :named_subject?, <<-PATTERN
40
- (block (send nil? :subject $sym) args ...)
41
- PATTERN
42
-
43
39
  def on_block(node)
44
40
  return unless example_group?(node)
45
41
 
@@ -62,6 +58,10 @@ module RuboCop
62
58
 
63
59
  private
64
60
 
61
+ def named_subject?(node)
62
+ node.send_node.arguments?
63
+ end
64
+
65
65
  def rename_autocorrect(node)
66
66
  lambda do |corrector|
67
67
  corrector.replace(node.send_node.loc.selector, 'let')
@@ -104,7 +104,7 @@ module RuboCop
104
104
  find_nested_contexts(node.parent) do |context, nesting|
105
105
  self.max = nesting
106
106
  add_offense(
107
- context.children.first,
107
+ context.send_node,
108
108
  location: :expression,
109
109
  message: message(nesting)
110
110
  )
@@ -64,7 +64,7 @@ module RuboCop
64
64
  end
65
65
 
66
66
  def skip_symbol?(symbol_node)
67
- symbol_node == SKIP_SYMBOL || symbol_node == PENDING_SYMBOL
67
+ [SKIP_SYMBOL, PENDING_SYMBOL].include?(symbol_node)
68
68
  end
69
69
  end
70
70
  end
@@ -0,0 +1,43 @@
1
+ module RuboCop
2
+ module Cop
3
+ module RSpec
4
+ # Prefer `not_to receive(...)` over `receive(...).never`.
5
+ #
6
+ # @example
7
+ #
8
+ # # bad
9
+ # expect(foo).to receive(:bar).never
10
+ #
11
+ # # good
12
+ # expect(foo).not_to receive(:bar)
13
+ #
14
+ class ReceiveNever < Cop
15
+ include RangeHelp
16
+
17
+ MSG = 'Use `not_to receive` instead of `never`.'.freeze
18
+
19
+ def_node_search :method_on_stub?, '(send nil? :receive ...)'
20
+
21
+ def on_send(node)
22
+ return unless node.method_name == :never && method_on_stub?(node)
23
+
24
+ add_offense(
25
+ node,
26
+ location: :selector
27
+ )
28
+ end
29
+
30
+ def autocorrect(node)
31
+ lambda do |corrector|
32
+ corrector.replace(node.parent.loc.selector, 'not_to')
33
+ range = range_between(
34
+ node.loc.dot.begin_pos,
35
+ node.loc.selector.end_pos
36
+ )
37
+ corrector.remove(range)
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
@@ -29,8 +29,6 @@ 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?, Helpers::ALL.block_pattern
33
-
34
32
  def on_block(node)
35
33
  return unless example_group_with_body?(node)
36
34
 
@@ -68,11 +68,11 @@ module RuboCop
68
68
 
69
69
  def on_block(node)
70
70
  context_with_only_examples(node) do
71
- add_shared_item_offense(node, MSG_EXAMPLES)
71
+ add_shared_item_offense(node.send_node, MSG_EXAMPLES)
72
72
  end
73
73
 
74
74
  examples_with_only_context(node) do
75
- add_shared_item_offense(node, MSG_CONTEXT)
75
+ add_shared_item_offense(node.send_node, MSG_CONTEXT)
76
76
  end
77
77
  end
78
78
 
@@ -100,7 +100,7 @@ module RuboCop
100
100
 
101
101
  def add_shared_item_offense(node, message)
102
102
  add_offense(
103
- node.children.first,
103
+ node,
104
104
  location: :expression,
105
105
  message: message
106
106
  )
@@ -44,7 +44,7 @@ module RuboCop
44
44
  parent = expect.parent
45
45
  return true unless parent
46
46
  return true if parent.begin_type?
47
- return true if parent.block_type? && parent.children[2] == expect
47
+ return true if parent.block_type? && parent.body == expect
48
48
  end
49
49
  end
50
50
  end
@@ -1,9 +1,8 @@
1
1
  require_relative 'rspec/capybara/current_path_expectation'
2
2
  require_relative 'rspec/capybara/feature_methods'
3
3
 
4
+ require_relative 'rspec/factory_bot/attribute_defined_statically'
4
5
  require_relative 'rspec/factory_bot/create_list'
5
- require_relative 'rspec/factory_bot/dynamic_attribute_defined_statically'
6
- require_relative 'rspec/factory_bot/static_attribute_defined_dynamically'
7
6
 
8
7
  begin
9
8
  require_relative 'rspec/rails/http_status'
@@ -20,9 +19,9 @@ require_relative 'rspec/be_eql'
20
19
  require_relative 'rspec/before_after_all'
21
20
  require_relative 'rspec/context_wording'
22
21
  require_relative 'rspec/describe_class'
23
- require_relative 'rspec/described_class'
24
22
  require_relative 'rspec/describe_method'
25
23
  require_relative 'rspec/describe_symbol'
24
+ require_relative 'rspec/described_class'
26
25
  require_relative 'rspec/empty_example_group'
27
26
  require_relative 'rspec/empty_line_after_example_group'
28
27
  require_relative 'rspec/empty_line_after_final_let'
@@ -50,6 +49,7 @@ require_relative 'rspec/let_setup'
50
49
  require_relative 'rspec/message_chain'
51
50
  require_relative 'rspec/message_expectation'
52
51
  require_relative 'rspec/message_spies'
52
+ require_relative 'rspec/missing_example_group_argument'
53
53
  require_relative 'rspec/multiple_describes'
54
54
  require_relative 'rspec/multiple_expectations'
55
55
  require_relative 'rspec/multiple_subjects'
@@ -60,6 +60,7 @@ require_relative 'rspec/overwriting_setup'
60
60
  require_relative 'rspec/pending'
61
61
  require_relative 'rspec/predicate_matcher'
62
62
  require_relative 'rspec/receive_counts'
63
+ require_relative 'rspec/receive_never'
63
64
  require_relative 'rspec/repeated_description'
64
65
  require_relative 'rspec/repeated_example'
65
66
  require_relative 'rspec/return_from_stub'
@@ -4,9 +4,7 @@ module RuboCop
4
4
  module RSpec
5
5
  # Shared behavior for aligning braces for single line lets
6
6
  class AlignLetBrace
7
- extend NodePattern::Macros
8
-
9
- def_node_matcher :let?, Language::Helpers::ALL.block_pattern
7
+ include RuboCop::RSpec::Language::NodePattern
10
8
 
11
9
  def initialize(root, token)
12
10
  @root = root
@@ -25,6 +25,12 @@ module RuboCop
25
25
  source_range(processed_source.buffer, last_line, start, content_length)
26
26
  end
27
27
 
28
+ def last_child?(node)
29
+ return true unless node.parent && node.parent.begin_type?
30
+
31
+ node.equal?(node.parent.children.last)
32
+ end
33
+
28
34
  def autocorrect(node)
29
35
  lambda do |corrector|
30
36
  missing_separating_line(node) do |location|
@@ -14,13 +14,6 @@ module RuboCop
14
14
  ExampleGroups::ALL + SharedGroups::ALL + Includes::ALL
15
15
  ).block_pattern
16
16
 
17
- # @!method hook?(node)
18
- #
19
- # Detect if node is `before`, `after`, `around`
20
- def_node_matcher :hook?, Hooks::ALL.block_pattern
21
-
22
- def_node_matcher :subject?, Subject::ALL.block_pattern
23
-
24
17
  def subjects
25
18
  subjects_in_scope(node)
26
19
  end
@@ -14,6 +14,12 @@ module RuboCop
14
14
  PATTERN
15
15
 
16
16
  def_node_matcher :example?, Examples::ALL.block_pattern
17
+
18
+ def_node_matcher :hook?, Hooks::ALL.block_pattern
19
+
20
+ def_node_matcher :let?, Helpers::ALL.block_pattern
21
+
22
+ def_node_matcher :subject?, Subject::ALL.block_pattern
17
23
  end
18
24
  end
19
25
  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.27.0'.freeze
7
+ STRING = '1.28.0'.freeze
8
8
  end
9
9
  end
10
10
  end
@@ -19,7 +19,7 @@ Gem::Specification.new do |spec|
19
19
 
20
20
  spec.version = RuboCop::RSpec::Version::STRING
21
21
  spec.platform = Gem::Platform::RUBY
22
- spec.required_ruby_version = '>= 2.1.0'
22
+ spec.required_ruby_version = '>= 2.2.0'
23
23
 
24
24
  spec.require_paths = ['lib']
25
25
  spec.files = Dir[
@@ -37,7 +37,7 @@ Gem::Specification.new do |spec|
37
37
  'documentation_uri' => 'https://rubocop-rspec.readthedocs.io/'
38
38
  }
39
39
 
40
- spec.add_runtime_dependency 'rubocop', '>= 0.56.0'
40
+ spec.add_runtime_dependency 'rubocop', '>= 0.58.0'
41
41
 
42
42
  spec.add_development_dependency 'rack'
43
43
  spec.add_development_dependency 'rake'
@@ -1,8 +1,18 @@
1
1
  RSpec.describe 'Project requires' do
2
2
  it 'alphabetizes cop requires' do
3
- source = SpecHelper::ROOT.join('lib', 'rubocop-rspec.rb').read
4
- requires = source.split("\n").grep(%r{rubocop/cop/rspec/[^(?:cop)]})
3
+ source = SpecHelper::ROOT.join('lib', 'rubocop', 'cop', 'rspec_cops.rb')
4
+ captures = source.read.scan(%r{^(require_relative 'rspec/(.*?/)?(.*?)')$})
5
5
 
6
- expect(requires.join("\n")).to eql(requires.sort.join("\n"))
6
+ require_statements = captures.map(&:first)
7
+ sorted_require_statements =
8
+ captures.sort_by do |_require_statement, cop_category, name|
9
+ [cop_category || 'rspec', name]
10
+ end.map(&:first)
11
+
12
+ aggregate_failures do
13
+ # Sanity check that we actually discovered require statements.
14
+ expect(captures).not_to be_empty
15
+ expect(require_statements).to eql(sorted_require_statements)
16
+ end
7
17
  end
8
18
  end
@@ -1,5 +1,5 @@
1
- RSpec.describe RuboCop::Cop::RSpec::BeforeAfterAll, :config do
2
- subject(:cop) { described_class.new(config) }
1
+ RSpec.describe RuboCop::Cop::RSpec::BeforeAfterAll do
2
+ subject(:cop) { described_class.new }
3
3
 
4
4
  def message(hook)
5
5
  "Beware of using `#{hook}` as it may cause state to leak between tests. "\
@@ -62,6 +62,21 @@ RSpec.describe RuboCop::Cop::RSpec::EmptyLineAfterExampleGroup do
62
62
  RUBY
63
63
  end
64
64
 
65
+ it 'handles describes in an if block' do
66
+ expect_offense(<<-RUBY)
67
+ if RUBY_VERSION < 2.3
68
+ describe 'skips checks under old ruby' do
69
+ end
70
+ else
71
+ describe 'first check' do
72
+ end
73
+ ^^^ Add an empty line after `describe`.
74
+ describe 'second check' do
75
+ end
76
+ end
77
+ RUBY
78
+ end
79
+
65
80
  bad_example = <<-RUBY
66
81
  RSpec.describe Foo do
67
82
  describe '#bar' do
@@ -0,0 +1,156 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe RuboCop::Cop::RSpec::FactoryBot::AttributeDefinedStatically do # rubocop:disable Metrics/LineLength
4
+ subject(:cop) { described_class.new }
5
+
6
+ it 'registers an offense for offending code' do
7
+ expect_offense(<<-RUBY)
8
+ FactoryBot.define do
9
+ factory :post do
10
+ title "Something"
11
+ ^^^^^^^^^^^^^^^^^ Use a block to declare attribute values.
12
+ published_at 1.day.from_now
13
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^ Use a block to declare attribute values.
14
+ status [:draft, :published].sample
15
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Use a block to declare attribute values.
16
+ created_at 1.day.ago
17
+ ^^^^^^^^^^^^^^^^^^^^ Use a block to declare attribute values.
18
+ update_times [Time.current]
19
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^ Use a block to declare attribute values.
20
+ meta_tags(foo: Time.current)
21
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Use a block to declare attribute values.
22
+ end
23
+ end
24
+ RUBY
25
+ end
26
+
27
+ it 'registers an offense in a trait' do
28
+ expect_offense(<<-RUBY)
29
+ FactoryBot.define do
30
+ factory :post do
31
+ trait :published do
32
+ title "Something"
33
+ ^^^^^^^^^^^^^^^^^ Use a block to declare attribute values.
34
+ published_at 1.day.from_now
35
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^ Use a block to declare attribute values.
36
+ end
37
+ end
38
+ end
39
+ RUBY
40
+ end
41
+
42
+ it 'registers an offense in a transient block' do
43
+ expect_offense(<<-RUBY)
44
+ FactoryBot.define do
45
+ factory :post do
46
+ transient do
47
+ title "Something"
48
+ ^^^^^^^^^^^^^^^^^ Use a block to declare attribute values.
49
+ published_at 1.day.from_now
50
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^ Use a block to declare attribute values.
51
+ end
52
+ end
53
+ end
54
+ RUBY
55
+ end
56
+
57
+ it 'accepts valid factory definitions' do
58
+ expect_no_offenses(<<-RUBY)
59
+ FactoryBot.define do
60
+ factory :post do
61
+ trait :published do
62
+ published_at { 1.day.from_now }
63
+ end
64
+ created_at { 1.day.ago }
65
+ status { :draft }
66
+ comments_count { 0 }
67
+ title { "Static" }
68
+ description { FFaker::Lorem.paragraph(10) }
69
+ recent_statuses { [] }
70
+ tags { { like_count: 2 } }
71
+
72
+ before(:create, &:initialize_something)
73
+ after(:create, &:rebuild_cache)
74
+ end
75
+ end
76
+ RUBY
77
+ end
78
+
79
+ it 'does not add offense if out of factory bot block' do
80
+ expect_no_offenses(<<-RUBY)
81
+ status [:draft, :published].sample
82
+ published_at 1.day.from_now
83
+ created_at 1.day.ago
84
+ update_times [Time.current]
85
+ meta_tags(foo: Time.current)
86
+ RUBY
87
+ end
88
+
89
+ it 'accepts valid association definitions' do
90
+ expect_no_offenses(<<-RUBY)
91
+ FactoryBot.define do
92
+ factory :post do
93
+ author age: 42, factory: :user
94
+ end
95
+ end
96
+ RUBY
97
+ end
98
+
99
+ it 'accepts valid sequence definition' do
100
+ expect_no_offenses(<<-RUBY)
101
+ FactoryBot.define do
102
+ factory :post do
103
+ sequence :negative_numbers, &:-@
104
+ end
105
+ end
106
+ RUBY
107
+ end
108
+
109
+ bad = <<-RUBY
110
+ FactoryBot.define do
111
+ factory :post do
112
+ title "Something"
113
+ comments_count 0
114
+ tag Tag::MAGIC
115
+ recent_statuses []
116
+ status([:draft, :published].sample)
117
+ published_at 1.day.from_now
118
+ created_at(1.day.ago)
119
+ updated_at Time.current
120
+ update_times [Time.current]
121
+ meta_tags(foo: Time.current)
122
+ other_tags({ foo: Time.current })
123
+ options color: :blue
124
+
125
+ trait :old do
126
+ published_at 1.week.ago
127
+ end
128
+ end
129
+ end
130
+ RUBY
131
+
132
+ corrected = <<-RUBY
133
+ FactoryBot.define do
134
+ factory :post do
135
+ title { "Something" }
136
+ comments_count { 0 }
137
+ tag { Tag::MAGIC }
138
+ recent_statuses { [] }
139
+ status { [:draft, :published].sample }
140
+ published_at { 1.day.from_now }
141
+ created_at { 1.day.ago }
142
+ updated_at { Time.current }
143
+ update_times { [Time.current] }
144
+ meta_tags { { foo: Time.current } }
145
+ other_tags { { foo: Time.current } }
146
+ options { { color: :blue } }
147
+
148
+ trait :old do
149
+ published_at { 1.week.ago }
150
+ end
151
+ end
152
+ end
153
+ RUBY
154
+
155
+ include_examples 'autocorrect', bad, corrected
156
+ end