rubocop-rspec 1.27.0 → 1.28.0

Sign up to get free protection for your applications and to get access to all the features.
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