rubocop-rspec 1.37.1 → 1.41.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (49) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +53 -0
  3. data/CODE_OF_CONDUCT.md +17 -0
  4. data/README.md +2 -62
  5. data/config/default.yml +155 -15
  6. data/lib/rubocop-rspec.rb +3 -1
  7. data/lib/rubocop/cop/rspec/capybara/visibility_matcher.rb +69 -0
  8. data/lib/rubocop/cop/rspec/context_wording.rb +5 -1
  9. data/lib/rubocop/cop/rspec/cop.rb +9 -29
  10. data/lib/rubocop/cop/rspec/describe_class.rb +20 -5
  11. data/lib/rubocop/cop/rspec/describe_method.rb +0 -1
  12. data/lib/rubocop/cop/rspec/empty_hook.rb +50 -0
  13. data/lib/rubocop/cop/rspec/expect_actual.rb +27 -2
  14. data/lib/rubocop/cop/rspec/factory_bot/attribute_defined_statically.rb +5 -2
  15. data/lib/rubocop/cop/rspec/file_path.rb +32 -4
  16. data/lib/rubocop/cop/rspec/hooks_before_examples.rb +3 -21
  17. data/lib/rubocop/cop/rspec/instance_variable.rb +16 -11
  18. data/lib/rubocop/cop/rspec/leading_subject.rb +3 -10
  19. data/lib/rubocop/cop/rspec/leaky_constant_declaration.rb +1 -4
  20. data/lib/rubocop/cop/rspec/let_before_examples.rb +3 -21
  21. data/lib/rubocop/cop/rspec/let_setup.rb +15 -3
  22. data/lib/rubocop/cop/rspec/named_subject.rb +6 -6
  23. data/lib/rubocop/cop/rspec/nested_groups.rb +9 -10
  24. data/lib/rubocop/cop/rspec/predicate_matcher.rb +2 -2
  25. data/lib/rubocop/cop/rspec/rails/http_status.rb +2 -0
  26. data/lib/rubocop/cop/rspec/repeated_description.rb +17 -2
  27. data/lib/rubocop/cop/rspec/repeated_example_group_body.rb +97 -0
  28. data/lib/rubocop/cop/rspec/repeated_example_group_description.rb +96 -0
  29. data/lib/rubocop/cop/rspec/return_from_stub.rb +3 -2
  30. data/lib/rubocop/cop/rspec/scattered_let.rb +13 -0
  31. data/lib/rubocop/cop/rspec/scattered_setup.rb +27 -9
  32. data/lib/rubocop/cop/rspec/shared_examples.rb +1 -0
  33. data/lib/rubocop/cop/rspec/subject_stub.rb +23 -51
  34. data/lib/rubocop/cop/rspec/variable_definition.rb +56 -0
  35. data/lib/rubocop/cop/rspec/variable_name.rb +47 -0
  36. data/lib/rubocop/cop/rspec_cops.rb +7 -1
  37. data/lib/rubocop/rspec/corrector/move_node.rb +52 -0
  38. data/lib/rubocop/rspec/description_extractor.rb +2 -6
  39. data/lib/rubocop/rspec/example.rb +1 -1
  40. data/lib/rubocop/rspec/example_group.rb +21 -49
  41. data/lib/rubocop/rspec/factory_bot.rb +7 -1
  42. data/lib/rubocop/rspec/hook.rb +44 -11
  43. data/lib/rubocop/rspec/language.rb +20 -0
  44. data/lib/rubocop/rspec/language/node_pattern.rb +5 -1
  45. data/lib/rubocop/rspec/top_level_group.rb +44 -0
  46. data/lib/rubocop/rspec/variable.rb +16 -0
  47. data/lib/rubocop/rspec/version.rb +1 -1
  48. metadata +20 -11
  49. data/lib/rubocop/rspec/util.rb +0 -19
@@ -47,27 +47,30 @@ module RuboCop
47
47
  # end
48
48
  #
49
49
  class InstanceVariable < Cop
50
+ include RuboCop::RSpec::TopLevelGroup
51
+
50
52
  MSG = 'Avoid instance variables – use let, ' \
51
53
  'a method call, or a local variable (if possible).'
52
54
 
53
- EXAMPLE_GROUP_METHODS = ExampleGroups::ALL + SharedGroups::ALL
54
-
55
- def_node_matcher :spec_group?, EXAMPLE_GROUP_METHODS.block_pattern
56
-
57
55
  def_node_matcher :dynamic_class?, <<-PATTERN
58
56
  (block (send (const nil? :Class) :new ...) ...)
59
57
  PATTERN
60
58
 
59
+ def_node_matcher :custom_matcher?, <<-PATTERN
60
+ (block {
61
+ (send nil? :matcher sym)
62
+ (send (const (const nil? :RSpec) :Matchers) :define sym)
63
+ } ...)
64
+ PATTERN
65
+
61
66
  def_node_search :ivar_usage, '$(ivar $_)'
62
67
 
63
68
  def_node_search :ivar_assigned?, '(ivasgn % ...)'
64
69
 
65
- def on_block(node)
66
- return unless spec_group?(node)
67
-
70
+ def on_top_level_group(node)
68
71
  ivar_usage(node) do |ivar, name|
69
- return if inside_dynamic_class?(ivar)
70
- return if assignment_only? && !ivar_assigned?(node, name)
72
+ next if valid_usage?(ivar)
73
+ next if assignment_only? && !ivar_assigned?(node, name)
71
74
 
72
75
  add_offense(ivar)
73
76
  end
@@ -75,8 +78,10 @@ module RuboCop
75
78
 
76
79
  private
77
80
 
78
- def inside_dynamic_class?(node)
79
- node.each_ancestor(:block).any? { |block| dynamic_class?(block) }
81
+ def valid_usage?(node)
82
+ node.each_ancestor(:block).any? do |block|
83
+ dynamic_class?(block) || custom_matcher?(block)
84
+ end
80
85
  end
81
86
 
82
87
  def assignment_only?
@@ -32,8 +32,6 @@ module RuboCop
32
32
  # it { expect_something_else }
33
33
  #
34
34
  class LeadingSubject < Cop
35
- include RangeHelp
36
-
37
35
  MSG = 'Declare `subject` above any other `%<offending>s` declarations.'
38
36
 
39
37
  def on_block(node)
@@ -58,10 +56,9 @@ module RuboCop
58
56
  def autocorrect(node)
59
57
  lambda do |corrector|
60
58
  first_node = find_first_offending_node(node)
61
- first_node_position = first_node.loc.expression
62
- indent = "\n" + ' ' * first_node.loc.column
63
- corrector.insert_before(first_node_position, node.source + indent)
64
- corrector.remove(node_range(node))
59
+ RuboCop::RSpec::Corrector::MoveNode.new(
60
+ node, corrector, processed_source
61
+ ).move_before(first_node)
65
62
  end
66
63
  end
67
64
 
@@ -75,10 +72,6 @@ module RuboCop
75
72
  node.parent.children.find { |sibling| offending?(sibling) }
76
73
  end
77
74
 
78
- def node_range(node)
79
- range_by_whole_lines(node.source_range, include_final_newline: true)
80
- end
81
-
82
75
  def in_spec_block?(node)
83
76
  node.each_ancestor(:block).any? do |ancestor|
84
77
  example?(ancestor)
@@ -119,11 +119,8 @@ module RuboCop
119
119
  private
120
120
 
121
121
  def inside_describe_block?(node)
122
- node.each_ancestor(:block).any?(&method(:in_example_or_shared_group?))
122
+ node.each_ancestor(:block).any?(&method(:spec_group?))
123
123
  end
124
-
125
- def_node_matcher :in_example_or_shared_group?,
126
- (ExampleGroups::ALL + SharedGroups::ALL).block_pattern
127
124
  end
128
125
  end
129
126
  end
@@ -31,9 +31,6 @@ module RuboCop
31
31
  # expect(some).to be
32
32
  # end
33
33
  class LetBeforeExamples < Cop
34
- include RangeHelp
35
- include RuboCop::RSpec::FinalEndLocation
36
-
37
34
  MSG = 'Move `let` before the examples in the group.'
38
35
 
39
36
  def_node_matcher :example_or_group?, <<-PATTERN
@@ -52,11 +49,9 @@ module RuboCop
52
49
  def autocorrect(node)
53
50
  lambda do |corrector|
54
51
  first_example = find_first_example(node.parent)
55
- first_example_pos = first_example.loc.expression
56
- indent = "\n" + ' ' * first_example.loc.column
57
-
58
- corrector.insert_before(first_example_pos, source(node) + indent)
59
- corrector.remove(node_range_with_surrounding_space(node))
52
+ RuboCop::RSpec::Corrector::MoveNode.new(
53
+ node, corrector, processed_source
54
+ ).move_before(first_example)
60
55
  end
61
56
  end
62
57
 
@@ -80,19 +75,6 @@ module RuboCop
80
75
  def find_first_example(node)
81
76
  node.children.find { |sibling| example_or_group?(sibling) }
82
77
  end
83
-
84
- def node_range_with_surrounding_space(node)
85
- range = node_range(node)
86
- range_by_whole_lines(range, include_final_newline: true)
87
- end
88
-
89
- def source(node)
90
- node_range(node).source
91
- end
92
-
93
- def node_range(node)
94
- node.loc.expression.with(end_pos: final_end_location(node).end_pos)
95
- end
96
78
  end
97
79
  end
98
80
  end
@@ -28,14 +28,20 @@ module RuboCop
28
28
  class LetSetup < Cop
29
29
  MSG = 'Do not use `let!` to setup objects not referenced in tests.'
30
30
 
31
- def_node_search :let_bang, <<-PATTERN
31
+ def_node_matcher :example_or_shared_group_or_including?,
32
+ (
33
+ ExampleGroups::ALL + SharedGroups::ALL +
34
+ Includes::ALL
35
+ ).block_pattern
36
+
37
+ def_node_matcher :let_bang, <<-PATTERN
32
38
  (block $(send nil? :let! (sym $_)) args ...)
33
39
  PATTERN
34
40
 
35
41
  def_node_search :method_called?, '(send nil? %)'
36
42
 
37
43
  def on_block(node)
38
- return unless example_group?(node)
44
+ return unless example_or_shared_group_or_including?(node)
39
45
 
40
46
  unused_let_bang(node) do |let|
41
47
  add_offense(let)
@@ -45,10 +51,16 @@ module RuboCop
45
51
  private
46
52
 
47
53
  def unused_let_bang(node)
48
- let_bang(node) do |method_send, method_name|
54
+ child_let_bang(node) do |method_send, method_name|
49
55
  yield(method_send) unless method_called?(node, method_name)
50
56
  end
51
57
  end
58
+
59
+ def child_let_bang(node, &block)
60
+ RuboCop::RSpec::ExampleGroup.new(node).lets.each do |let|
61
+ let_bang(let, &block)
62
+ end
63
+ end
52
64
  end
53
65
  end
54
66
  end
@@ -6,11 +6,11 @@ module RuboCop
6
6
  # Checks for explicitly referenced test subjects.
7
7
  #
8
8
  # RSpec lets you declare an "implicit subject" using `subject { ... }`
9
- # which allows for tests like `it { should be_valid }`. If you need to
10
- # reference your test subject you should explicitly name it using
11
- # `subject(:your_subject_name) { ... }`. Your test subjects should be
12
- # the most important object in your tests so they deserve a descriptive
13
- # name.
9
+ # which allows for tests like `it { is_expected.to be_valid }`.
10
+ # If you need to reference your test subject you should explicitly
11
+ # name it using `subject(:your_subject_name) { ... }`. Your test subjects
12
+ # should be the most important object in your tests so they deserve
13
+ # a descriptive name.
14
14
  #
15
15
  # This cop can be configured in your configuration using the
16
16
  # `IgnoreSharedExamples` which will not report offenses for implicit
@@ -39,7 +39,7 @@ module RuboCop
39
39
  # RSpec.describe Foo do
40
40
  # subject(:user) { described_class.new }
41
41
  #
42
- # it { should be_valid }
42
+ # it { is_expected.to be_valid }
43
43
  # end
44
44
  class NamedSubject < Cop
45
45
  MSG = 'Name your test subject if you need '\
@@ -97,13 +97,11 @@ module RuboCop
97
97
  "Configuration key `#{DEPRECATED_MAX_KEY}` for #{cop_name} is " \
98
98
  'deprecated in favor of `Max`. Please use that instead.'
99
99
 
100
- def_node_search :find_contexts, ExampleGroups::ALL.block_pattern
101
-
102
100
  def on_top_level_describe(node, _args)
103
- find_nested_contexts(node.parent) do |context, nesting|
101
+ find_nested_example_groups(node.parent) do |example_group, nesting|
104
102
  self.max = nesting
105
103
  add_offense(
106
- context.send_node,
104
+ example_group.send_node,
107
105
  message: message(nesting)
108
106
  )
109
107
  end
@@ -111,13 +109,14 @@ module RuboCop
111
109
 
112
110
  private
113
111
 
114
- def find_nested_contexts(node, nesting: 1, &block)
115
- find_contexts(node) do |nested_context|
116
- yield(nested_context, nesting) if nesting > max_nesting
112
+ def find_nested_example_groups(node, nesting: 1, &block)
113
+ example_group = example_group?(node)
114
+ yield node, nesting if example_group && nesting > max_nesting
115
+
116
+ next_nesting = example_group ? nesting + 1 : nesting
117
117
 
118
- nested_context.each_child_node do |child|
119
- find_nested_contexts(child, nesting: nesting + 1, &block)
120
- end
118
+ node.each_child_node(:block, :begin) do |child|
119
+ find_nested_example_groups(child, nesting: next_nesting, &block)
121
120
  end
122
121
  end
123
122
 
@@ -277,12 +277,12 @@ module RuboCop
277
277
  # expect(foo).to be_something
278
278
  #
279
279
  # # also good - It checks "true" strictly.
280
- # expect(foo).to be(true)
280
+ # expect(foo.something?).to be(true)
281
281
  #
282
282
  # @example Strict: false, EnforcedStyle: inflected
283
283
  # # bad
284
284
  # expect(foo.something?).to be_truthy
285
- # expect(foo).to be(true)
285
+ # expect(foo.something?).to be(true)
286
286
  #
287
287
  # # good
288
288
  # expect(foo).to be_something
@@ -70,6 +70,7 @@ module RuboCop
70
70
  'to describe HTTP status code.'
71
71
 
72
72
  attr_reader :node
73
+
73
74
  def initialize(node)
74
75
  @node = node
75
76
  end
@@ -110,6 +111,7 @@ module RuboCop
110
111
  ALLOWED_STATUSES = %i[error success missing redirect].freeze
111
112
 
112
113
  attr_reader :node
114
+
113
115
  def initialize(node)
114
116
  @node = node
115
117
  end
@@ -29,6 +29,17 @@ module RuboCop
29
29
  # end
30
30
  # end
31
31
  #
32
+ # # good
33
+ # RSpec.describe User do
34
+ # it 'is valid' do
35
+ # # ...
36
+ # end
37
+ #
38
+ # it 'is valid', :flag do
39
+ # # ...
40
+ # end
41
+ # end
42
+ #
32
43
  class RepeatedDescription < Cop
33
44
  MSG = "Don't repeat descriptions within an example group."
34
45
 
@@ -47,14 +58,18 @@ module RuboCop
47
58
  grouped_examples =
48
59
  RuboCop::RSpec::ExampleGroup.new(node)
49
60
  .examples
50
- .group_by(&:doc_string)
61
+ .group_by { |example| example_signature(example) }
51
62
 
52
63
  grouped_examples
53
- .select { |description, group| description && group.size > 1 }
64
+ .select { |signatures, group| signatures.any? && group.size > 1 }
54
65
  .values
55
66
  .flatten
56
67
  .map(&:definition)
57
68
  end
69
+
70
+ def example_signature(example)
71
+ [example.metadata, example.doc_string]
72
+ end
58
73
  end
59
74
  end
60
75
  end
@@ -0,0 +1,97 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module RSpec
6
+ # Check for repeated describe and context block body.
7
+ #
8
+ # @example
9
+ #
10
+ # # bad
11
+ # describe 'cool feature x' do
12
+ # it { cool_predicate }
13
+ # end
14
+ #
15
+ # describe 'cool feature y' do
16
+ # it { cool_predicate }
17
+ # end
18
+ #
19
+ # # good
20
+ # describe 'cool feature' do
21
+ # it { cool_predicate }
22
+ # end
23
+ #
24
+ # describe 'another cool feature' do
25
+ # it { another_predicate }
26
+ # end
27
+ #
28
+ # # good
29
+ # context 'when case x', :tag do
30
+ # it { cool_predicate }
31
+ # end
32
+ #
33
+ # context 'when case y' do
34
+ # it { cool_predicate }
35
+ # end
36
+ #
37
+ # # good
38
+ # context Array do
39
+ # it { is_expected.to respond_to :each }
40
+ # end
41
+ #
42
+ # context Hash do
43
+ # it { is_expected.to respond_to :each }
44
+ # end
45
+ #
46
+ class RepeatedExampleGroupBody < Cop
47
+ MSG = 'Repeated %<group>s block body on line(s) %<loc>s'
48
+
49
+ def_node_matcher :several_example_groups?, <<-PATTERN
50
+ (begin <#example_group_with_body? #example_group_with_body? ...>)
51
+ PATTERN
52
+
53
+ def_node_matcher :metadata, '(block (send _ _ _ $...) ...)'
54
+ def_node_matcher :body, '(block _ args $...)'
55
+ def_node_matcher :const_arg, '(block (send _ _ $const ...) ...)'
56
+
57
+ def_node_matcher :skip_or_pending?, <<-PATTERN
58
+ (block <(send nil? {:skip :pending}) ...>)
59
+ PATTERN
60
+
61
+ def on_begin(node)
62
+ return unless several_example_groups?(node)
63
+
64
+ repeated_group_bodies(node).each do |group, repeats|
65
+ add_offense(group, message: message(group, repeats))
66
+ end
67
+ end
68
+
69
+ private
70
+
71
+ def repeated_group_bodies(node)
72
+ node
73
+ .children
74
+ .select { |child| example_group_with_body?(child) }
75
+ .reject { |child| skip_or_pending?(child) }
76
+ .group_by { |group| signature_keys(group) }
77
+ .values
78
+ .reject(&:one?)
79
+ .flat_map { |groups| add_repeated_lines(groups) }
80
+ end
81
+
82
+ def add_repeated_lines(groups)
83
+ repeated_lines = groups.map(&:first_line)
84
+ groups.map { |group| [group, repeated_lines - [group.first_line]] }
85
+ end
86
+
87
+ def signature_keys(group)
88
+ [metadata(group), body(group), const_arg(group)]
89
+ end
90
+
91
+ def message(group, repeats)
92
+ format(MSG, group: group.method_name, loc: repeats)
93
+ end
94
+ end
95
+ end
96
+ end
97
+ end
@@ -0,0 +1,96 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module RSpec
6
+ # Check for repeated example group descriptions.
7
+ #
8
+ # @example
9
+ #
10
+ # # bad
11
+ # describe 'cool feature' do
12
+ # # example group
13
+ # end
14
+ #
15
+ # describe 'cool feature' do
16
+ # # example group
17
+ # end
18
+ #
19
+ # # bad
20
+ # context 'when case x' do
21
+ # # example group
22
+ # end
23
+ #
24
+ # describe 'when case x' do
25
+ # # example group
26
+ # end
27
+ #
28
+ # # good
29
+ # describe 'cool feature' do
30
+ # # example group
31
+ # end
32
+ #
33
+ # describe 'another cool feature' do
34
+ # # example group
35
+ # end
36
+ #
37
+ # # good
38
+ # context 'when case x' do
39
+ # # example group
40
+ # end
41
+ #
42
+ # context 'when another case' do
43
+ # # example group
44
+ # end
45
+ #
46
+ class RepeatedExampleGroupDescription < Cop
47
+ MSG = 'Repeated %<group>s block description on line(s) %<loc>s'
48
+
49
+ def_node_matcher :several_example_groups?, <<-PATTERN
50
+ (begin <#example_group? #example_group? ...>)
51
+ PATTERN
52
+
53
+ def_node_matcher :doc_string_and_metadata, <<-PATTERN
54
+ (block (send _ _ $_ $...) ...)
55
+ PATTERN
56
+
57
+ def_node_matcher :skip_or_pending?, <<-PATTERN
58
+ (block <(send nil? {:skip :pending}) ...>)
59
+ PATTERN
60
+
61
+ def_node_matcher :empty_description?, '(block (send _ _) ...)'
62
+
63
+ def on_begin(node)
64
+ return unless several_example_groups?(node)
65
+
66
+ repeated_group_descriptions(node).each do |group, repeats|
67
+ add_offense(group, message: message(group, repeats))
68
+ end
69
+ end
70
+
71
+ private
72
+
73
+ def repeated_group_descriptions(node)
74
+ node
75
+ .children
76
+ .select { |child| example_group?(child) }
77
+ .reject { |child| skip_or_pending?(child) }
78
+ .reject { |child| empty_description?(child) }
79
+ .group_by { |group| doc_string_and_metadata(group) }
80
+ .values
81
+ .reject(&:one?)
82
+ .flat_map { |groups| add_repeated_lines(groups) }
83
+ end
84
+
85
+ def add_repeated_lines(groups)
86
+ repeated_lines = groups.map(&:first_line)
87
+ groups.map { |group| [group, repeated_lines - [group.first_line]] }
88
+ end
89
+
90
+ def message(group, repeats)
91
+ format(MSG, group: group.method_name, loc: repeats)
92
+ end
93
+ end
94
+ end
95
+ end
96
+ end