rubocop-rspec 1.33.0 → 1.34.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (64) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +16 -2
  3. data/README.md +10 -2
  4. data/config/default.yml +6 -1
  5. data/lib/rubocop/cop/rspec/any_instance.rb +0 -1
  6. data/lib/rubocop/cop/rspec/around_block.rb +1 -2
  7. data/lib/rubocop/cop/rspec/before_after_all.rb +0 -1
  8. data/lib/rubocop/cop/rspec/context_wording.rb +18 -17
  9. data/lib/rubocop/cop/rspec/describe_class.rb +1 -1
  10. data/lib/rubocop/cop/rspec/describe_method.rb +1 -1
  11. data/lib/rubocop/cop/rspec/describe_symbol.rb +1 -1
  12. data/lib/rubocop/cop/rspec/described_class.rb +79 -13
  13. data/lib/rubocop/cop/rspec/empty_example_group.rb +1 -1
  14. data/lib/rubocop/cop/rspec/empty_line_after_subject.rb +1 -1
  15. data/lib/rubocop/cop/rspec/example_length.rb +1 -1
  16. data/lib/rubocop/cop/rspec/example_wording.rb +6 -4
  17. data/lib/rubocop/cop/rspec/expect_actual.rb +1 -1
  18. data/lib/rubocop/cop/rspec/expect_output.rb +2 -0
  19. data/lib/rubocop/cop/rspec/factory_bot/attribute_defined_statically.rb +1 -1
  20. data/lib/rubocop/cop/rspec/factory_bot/create_list.rb +1 -2
  21. data/lib/rubocop/cop/rspec/file_path.rb +0 -1
  22. data/lib/rubocop/cop/rspec/focus.rb +1 -1
  23. data/lib/rubocop/cop/rspec/hook_argument.rb +0 -1
  24. data/lib/rubocop/cop/rspec/instance_spy.rb +1 -1
  25. data/lib/rubocop/cop/rspec/instance_variable.rb +1 -1
  26. data/lib/rubocop/cop/rspec/invalid_predicate_matcher.rb +1 -1
  27. data/lib/rubocop/cop/rspec/it_behaves_like.rb +1 -1
  28. data/lib/rubocop/cop/rspec/iterated_expectation.rb +1 -1
  29. data/lib/rubocop/cop/rspec/leading_subject.rb +0 -1
  30. data/lib/rubocop/cop/rspec/leaky_constant_declaration.rb +128 -0
  31. data/lib/rubocop/cop/rspec/let_before_examples.rb +1 -1
  32. data/lib/rubocop/cop/rspec/let_setup.rb +2 -4
  33. data/lib/rubocop/cop/rspec/missing_example_group_argument.rb +1 -2
  34. data/lib/rubocop/cop/rspec/multiple_describes.rb +1 -1
  35. data/lib/rubocop/cop/rspec/multiple_expectations.rb +32 -16
  36. data/lib/rubocop/cop/rspec/multiple_subjects.rb +1 -1
  37. data/lib/rubocop/cop/rspec/nested_groups.rb +0 -1
  38. data/lib/rubocop/cop/rspec/overwriting_setup.rb +0 -1
  39. data/lib/rubocop/cop/rspec/pending.rb +1 -1
  40. data/lib/rubocop/cop/rspec/predicate_matcher.rb +0 -3
  41. data/lib/rubocop/cop/rspec/repeated_description.rb +1 -1
  42. data/lib/rubocop/cop/rspec/repeated_example.rb +1 -1
  43. data/lib/rubocop/cop/rspec/scattered_let.rb +1 -1
  44. data/lib/rubocop/cop/rspec/scattered_setup.rb +1 -1
  45. data/lib/rubocop/cop/rspec/shared_context.rb +0 -1
  46. data/lib/rubocop/cop/rspec/single_argument_message_chain.rb +1 -1
  47. data/lib/rubocop/cop/rspec/subject_stub.rb +17 -20
  48. data/lib/rubocop/cop/rspec/unspecified_exception.rb +1 -4
  49. data/lib/rubocop/cop/rspec/verified_doubles.rb +1 -1
  50. data/lib/rubocop/cop/rspec/void_expect.rb +1 -1
  51. data/lib/rubocop/cop/rspec_cops.rb +1 -0
  52. data/lib/rubocop/rspec/language.rb +1 -1
  53. data/lib/rubocop/rspec/top_level_describe.rb +0 -4
  54. data/lib/rubocop/rspec/version.rb +1 -1
  55. data/spec/rubocop/cop/rspec/cop_spec.rb +3 -3
  56. data/spec/rubocop/cop/rspec/describe_class_spec.rb +7 -0
  57. data/spec/rubocop/cop/rspec/described_class_spec.rb +113 -80
  58. data/spec/rubocop/cop/rspec/example_wording_spec.rb +33 -0
  59. data/spec/rubocop/cop/rspec/leaky_constant_declaration_spec.rb +91 -0
  60. data/spec/rubocop/cop/rspec/let_setup_spec.rb +2 -2
  61. data/spec/rubocop/cop/rspec/multiple_expectations_spec.rb +64 -37
  62. data/spec/rubocop/cop/rspec/subject_stub_spec.rb +113 -14
  63. data/spec/rubocop/rspec/language/selector_set_spec.rb +2 -2
  64. metadata +5 -2
@@ -59,7 +59,6 @@ module RuboCop
59
59
 
60
60
  add_offense(
61
61
  node,
62
- location: :expression,
63
62
  message: format(MSG, suffix: glob)
64
63
  )
65
64
  end
@@ -44,7 +44,7 @@ module RuboCop
44
44
 
45
45
  def on_send(node)
46
46
  focus_metadata(node) do |focus|
47
- add_offense(focus, location: :expression)
47
+ add_offense(focus)
48
48
  end
49
49
  end
50
50
 
@@ -80,7 +80,6 @@ module RuboCop
80
80
  style_detected(scope_name)
81
81
  add_offense(
82
82
  method_send,
83
- location: :expression,
84
83
  message: explicit_message(scope_name)
85
84
  )
86
85
  end
@@ -43,7 +43,7 @@ module RuboCop
43
43
 
44
44
  null_double(node) do |var, receiver|
45
45
  have_received_usage(node) do |expected|
46
- add_offense(receiver, location: :expression) if expected == var
46
+ add_offense(receiver) if expected == var
47
47
  end
48
48
  end
49
49
  end
@@ -68,7 +68,7 @@ module RuboCop
68
68
  return if inside_dynamic_class?(ivar)
69
69
  return if assignment_only? && !ivar_assigned?(node, name)
70
70
 
71
- add_offense(ivar, location: :expression)
71
+ add_offense(ivar)
72
72
  end
73
73
  end
74
74
 
@@ -24,7 +24,7 @@ module RuboCop
24
24
 
25
25
  def on_send(node)
26
26
  invalid_predicate_matcher?(node) do |predicate|
27
- add_offense(predicate, location: :expression)
27
+ add_offense(predicate)
28
28
  end
29
29
  end
30
30
 
@@ -28,7 +28,7 @@ module RuboCop
28
28
 
29
29
  def on_send(node)
30
30
  example_inclusion_offense(node, alternative_style) do
31
- add_offense(node, location: :expression)
31
+ add_offense(node)
32
32
  end
33
33
  end
34
34
 
@@ -34,7 +34,7 @@ module RuboCop
34
34
  def on_block(node)
35
35
  each?(node) do |arg, body|
36
36
  if single_expectation?(body, arg) || only_expectations?(body, arg)
37
- add_offense(node.send_node, location: :expression)
37
+ add_offense(node.send_node)
38
38
  end
39
39
  end
40
40
  end
@@ -47,7 +47,6 @@ module RuboCop
47
47
  if offending?(sibling)
48
48
  add_offense(
49
49
  node,
50
- location: :expression,
51
50
  message: format(MSG, offending: sibling.method_name)
52
51
  )
53
52
  end
@@ -0,0 +1,128 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module RSpec
6
+ # Checks that no class, module, or constant is declared.
7
+ #
8
+ # Constants, including classes and modules, when declared in a block
9
+ # scope, are defined in global namespace, and leak between examples.
10
+ #
11
+ # If several examples may define a `DummyClass`, instead of being a
12
+ # blank slate class as it will be in the first example, subsequent
13
+ # examples will be reopening it and modifying its behaviour in
14
+ # unpredictable ways.
15
+ # Even worse when a class that exists in the codebase is reopened.
16
+ #
17
+ # Anonymous classes are fine, since they don't result in global
18
+ # namespace name clashes.
19
+ #
20
+ # @see https://relishapp.com/rspec/rspec-mocks/docs/mutating-constants
21
+ #
22
+ # @example Constants leak between examples
23
+ # # bad
24
+ # describe SomeClass do
25
+ # OtherClass = Struct.new
26
+ # CONSTANT_HERE = 'I leak into global namespace'
27
+ # end
28
+ #
29
+ # # good
30
+ # describe SomeClass do
31
+ # before do
32
+ # stub_const('OtherClass', Struct.new)
33
+ # stub_const('CONSTANT_HERE', 'I only exist during this example')
34
+ # end
35
+ # end
36
+ #
37
+ # @example
38
+ # # bad
39
+ # describe SomeClass do
40
+ # class FooClass < described_class
41
+ # def double_that
42
+ # some_base_method * 2
43
+ # end
44
+ # end
45
+ #
46
+ # it { expect(FooClass.new.double_that).to eq(4) }
47
+ # end
48
+ #
49
+ # # good - anonymous class, no constant needs to be defined
50
+ # let(:foo_class) do
51
+ # Class.new(described_class) do
52
+ # def double_that
53
+ # some_base_method * 2
54
+ # end
55
+ # end
56
+ #
57
+ # it { expect(foo_class.new.double_that).to eq(4) }
58
+ # end
59
+ #
60
+ # # good - constant is stubbed
61
+ # describe SomeClass do
62
+ # before do
63
+ # foo_class = Class.new(described_class) do
64
+ # def do_something
65
+ # end
66
+ # end
67
+ # stub_const('FooClass', foo_class)
68
+ # end
69
+ #
70
+ # it { expect(FooClass.new.double_that).to eq(4) }
71
+ # end
72
+ #
73
+ # @example
74
+ # # bad
75
+ # describe SomeClass do
76
+ # module SomeModule
77
+ # class SomeClass
78
+ # def do_something
79
+ # end
80
+ # end
81
+ # end
82
+ # end
83
+ #
84
+ # # good
85
+ # describe SomeClass do
86
+ # before do
87
+ # foo_class = Class.new(described_class) do
88
+ # def do_something
89
+ # end
90
+ # end
91
+ # stub_const('SomeModule::SomeClass', foo_class)
92
+ # end
93
+ # end
94
+ class LeakyConstantDeclaration < Cop
95
+ MSG_CONST = 'Stub constant instead of declaring explicitly.'
96
+ MSG_CLASS = 'Stub class constant instead of declaring explicitly.'
97
+ MSG_MODULE = 'Stub module constant instead of declaring explicitly.'
98
+
99
+ def on_casgn(node)
100
+ return unless inside_describe_block?(node)
101
+
102
+ add_offense(node, message: MSG_CONST)
103
+ end
104
+
105
+ def on_class(node)
106
+ return unless inside_describe_block?(node)
107
+
108
+ add_offense(node, message: MSG_CLASS)
109
+ end
110
+
111
+ def on_module(node)
112
+ return unless inside_describe_block?(node)
113
+
114
+ add_offense(node, message: MSG_MODULE)
115
+ end
116
+
117
+ private
118
+
119
+ def inside_describe_block?(node)
120
+ node.each_ancestor(:block).any?(&method(:in_example_or_shared_group?))
121
+ end
122
+
123
+ def_node_matcher :in_example_or_shared_group?,
124
+ (ExampleGroups::ALL + SharedGroups::ALL).block_pattern
125
+ end
126
+ end
127
+ end
128
+ end
@@ -73,7 +73,7 @@ module RuboCop
73
73
  node.each_child_node do |child|
74
74
  next if child.sibling_index < first_example.sibling_index
75
75
 
76
- add_offense(child, location: :expression) if let?(child)
76
+ add_offense(child) if let?(child)
77
77
  end
78
78
  end
79
79
 
@@ -26,9 +26,7 @@ module RuboCop
26
26
  # expect(Widget.count).to eq(1)
27
27
  # end
28
28
  class LetSetup < Cop
29
- include RuboCop::RSpec::TopLevelDescribe
30
-
31
- MSG = 'Do not use `let!` for test setup.'
29
+ MSG = 'Do not use `let!` to setup objects not referenced in tests.'
32
30
 
33
31
  def_node_search :let_bang, <<-PATTERN
34
32
  (block $(send nil? :let! (sym $_)) args ...)
@@ -40,7 +38,7 @@ module RuboCop
40
38
  return unless example_group?(node)
41
39
 
42
40
  unused_let_bang(node) do |let|
43
- add_offense(let, location: :expression)
41
+ add_offense(let)
44
42
  end
45
43
  end
46
44
 
@@ -26,8 +26,7 @@ module RuboCop
26
26
  return unless example_group?(node)
27
27
  return if node.send_node.arguments?
28
28
 
29
- add_offense(node, location: :expression,
30
- message: format(MSG, method: node.method_name))
29
+ add_offense(node, message: format(MSG, method: node.method_name))
31
30
  end
32
31
  end
33
32
  end
@@ -32,7 +32,7 @@ module RuboCop
32
32
  return if single_top_level_describe?
33
33
  return unless top_level_nodes.first.equal?(node)
34
34
 
35
- add_offense(node, location: :expression)
35
+ add_offense(node)
36
36
  end
37
37
  end
38
38
  end
@@ -50,20 +50,20 @@ module RuboCop
50
50
 
51
51
  MSG = 'Example has too many expectations [%<total>d/%<max>d].'
52
52
 
53
- def_node_search :with_aggregated_failures?, '(sym :aggregate_failures)'
54
- def_node_search :disabled_aggregated_failures?, <<-PATTERN
53
+ def_node_search :with_aggregate_failures?, '(sym :aggregate_failures)'
54
+ def_node_search :disabled_aggregate_failures?, <<-PATTERN
55
55
  (pair (sym :aggregate_failures) (false))
56
56
  PATTERN
57
57
 
58
58
  def_node_matcher :expect?, Expectations::ALL.send_pattern
59
- def_node_matcher :aggregate_failures?, <<-PATTERN
59
+ def_node_matcher :aggregate_failures_block?, <<-PATTERN
60
60
  (block (send _ :aggregate_failures ...) ...)
61
61
  PATTERN
62
62
 
63
63
  def on_block(node)
64
64
  return unless example?(node)
65
65
 
66
- return if example_with_aggregated_failures?(node)
66
+ return if example_with_aggregate_failures?(node)
67
67
 
68
68
  expectations_count = to_enum(:find_expectation, node).count
69
69
 
@@ -76,19 +76,40 @@ module RuboCop
76
76
 
77
77
  private
78
78
 
79
- def example_with_aggregated_failures?(node)
80
- example = node.send_node
79
+ def example_with_aggregate_failures?(example_node)
80
+ node_with_aggregate_failures = find_aggregate_failures(example_node)
81
+ return false unless node_with_aggregate_failures
81
82
 
82
- (aggregated_failures_by_default? ||
83
- with_aggregated_failures?(example)) &&
84
- !disabled_aggregated_failures?(example)
83
+ aggregate_failures?(node_with_aggregate_failures)
84
+ end
85
+
86
+ def find_aggregate_failures(example_node)
87
+ example_node.send_node.each_ancestor(:block)
88
+ .find { |block_node| aggregate_failures_present?(block_node) }
89
+ end
90
+
91
+ def aggregate_failures_present?(node)
92
+ metadata(node)&.any?(&method(:with_aggregate_failures?))
93
+ end
94
+
95
+ def aggregate_failures?(example_or_group_node)
96
+ metadata(example_or_group_node)&.any? do |metadata|
97
+ with_aggregate_failures?(metadata) &&
98
+ !disabled_aggregate_failures?(metadata)
99
+ end
100
+ end
101
+
102
+ def metadata(example_or_group_node)
103
+ RuboCop::RSpec::Example
104
+ .new(example_or_group_node)
105
+ .metadata
85
106
  end
86
107
 
87
108
  def find_expectation(node, &block)
88
- yield if expect?(node) || aggregate_failures?(node)
109
+ yield if expect?(node) || aggregate_failures_block?(node)
89
110
 
90
111
  # do not search inside of aggregate_failures block
91
- return if aggregate_failures?(node)
112
+ return if aggregate_failures_block?(node)
92
113
 
93
114
  node.each_child_node do |child|
94
115
  find_expectation(child, &block)
@@ -98,7 +119,6 @@ module RuboCop
98
119
  def flag_example(node, expectation_count:)
99
120
  add_offense(
100
121
  node.send_node,
101
- location: :expression,
102
122
  message: format(
103
123
  MSG,
104
124
  total: expectation_count,
@@ -110,10 +130,6 @@ module RuboCop
110
130
  def max_expectations
111
131
  Integer(cop_config.fetch('Max', 1))
112
132
  end
113
-
114
- def aggregated_failures_by_default?
115
- cop_config.fetch('AggregateFailuresByDefault', false)
116
- end
117
133
  end
118
134
  end
119
135
  end
@@ -44,7 +44,7 @@ module RuboCop
44
44
  subjects = RuboCop::RSpec::ExampleGroup.new(node).subjects
45
45
 
46
46
  subjects[0...-1].each do |subject|
47
- add_offense(subject, location: :expression)
47
+ add_offense(subject)
48
48
  end
49
49
  end
50
50
 
@@ -104,7 +104,6 @@ module RuboCop
104
104
  self.max = nesting
105
105
  add_offense(
106
106
  context.send_node,
107
- location: :expression,
108
107
  message: message(nesting)
109
108
  )
110
109
  end
@@ -33,7 +33,6 @@ module RuboCop
33
33
  find_duplicates(node.body) do |duplicate, name|
34
34
  add_offense(
35
35
  duplicate,
36
- location: :expression,
37
36
  message: format(MSG, name: name)
38
37
  )
39
38
  end
@@ -46,7 +46,7 @@ module RuboCop
46
46
  def on_send(node)
47
47
  return unless pending_block?(node) || skipped_from_metadata?(node)
48
48
 
49
- add_offense(node, location: :expression)
49
+ add_offense(node)
50
50
  end
51
51
 
52
52
  private
@@ -17,7 +17,6 @@ module RuboCop
17
17
  predicate_in_actual?(node) do |predicate|
18
18
  add_offense(
19
19
  node,
20
- location: :expression,
21
20
  message: message_inflected(predicate)
22
21
  )
23
22
  end
@@ -143,7 +142,6 @@ module RuboCop
143
142
  predicate_matcher_block?(node) do |_actual, matcher|
144
143
  add_offense(
145
144
  node,
146
- location: :expression,
147
145
  message: message_explicit(matcher)
148
146
  )
149
147
  ignore_node(node.children.first)
@@ -155,7 +153,6 @@ module RuboCop
155
153
  predicate_matcher?(node) do |_actual, matcher|
156
154
  add_offense(
157
155
  node,
158
- location: :expression,
159
156
  message: message_explicit(matcher)
160
157
  )
161
158
  end
@@ -36,7 +36,7 @@ module RuboCop
36
36
  return unless example_group?(node)
37
37
 
38
38
  repeated_descriptions(node).each do |repeated_description|
39
- add_offense(repeated_description, location: :expression)
39
+ add_offense(repeated_description)
40
40
  end
41
41
  end
42
42
 
@@ -22,7 +22,7 @@ module RuboCop
22
22
  return unless example_group?(node)
23
23
 
24
24
  repeated_examples(node).each do |repeated_example|
25
- add_offense(repeated_example, location: :expression)
25
+ add_offense(repeated_example)
26
26
  end
27
27
  end
28
28
 
@@ -44,7 +44,7 @@ module RuboCop
44
44
  lets.each_with_index do |node, idx|
45
45
  next if node.sibling_index == first_let.sibling_index + idx
46
46
 
47
- add_offense(node, location: :expression)
47
+ add_offense(node)
48
48
  end
49
49
  end
50
50
  end
@@ -29,7 +29,7 @@ module RuboCop
29
29
  return unless example_group?(node)
30
30
 
31
31
  analyzable_hooks(node).each do |repeated_hook|
32
- add_offense(repeated_hook, location: :expression)
32
+ add_offense(repeated_hook)
33
33
  end
34
34
  end
35
35