rubocop-rspec 1.37.0 → 1.40.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +52 -0
  3. data/README.md +2 -62
  4. data/config/default.yml +155 -15
  5. data/lib/rubocop-rspec.rb +2 -1
  6. data/lib/rubocop/cop/rspec/capybara/visibility_matcher.rb +69 -0
  7. data/lib/rubocop/cop/rspec/context_wording.rb +5 -1
  8. data/lib/rubocop/cop/rspec/cop.rb +9 -29
  9. data/lib/rubocop/cop/rspec/describe_class.rb +15 -2
  10. data/lib/rubocop/cop/rspec/describe_method.rb +0 -1
  11. data/lib/rubocop/cop/rspec/empty_hook.rb +50 -0
  12. data/lib/rubocop/cop/rspec/expect_actual.rb +27 -2
  13. data/lib/rubocop/cop/rspec/factory_bot/factory_class_name.rb +16 -1
  14. data/lib/rubocop/cop/rspec/file_path.rb +32 -4
  15. data/lib/rubocop/cop/rspec/hooks_before_examples.rb +3 -21
  16. data/lib/rubocop/cop/rspec/instance_variable.rb +13 -4
  17. data/lib/rubocop/cop/rspec/leading_subject.rb +3 -10
  18. data/lib/rubocop/cop/rspec/let_before_examples.rb +3 -21
  19. data/lib/rubocop/cop/rspec/message_chain.rb +1 -1
  20. data/lib/rubocop/cop/rspec/named_subject.rb +6 -6
  21. data/lib/rubocop/cop/rspec/rails/http_status.rb +2 -0
  22. data/lib/rubocop/cop/rspec/repeated_description.rb +17 -2
  23. data/lib/rubocop/cop/rspec/repeated_example_group_body.rb +97 -0
  24. data/lib/rubocop/cop/rspec/repeated_example_group_description.rb +96 -0
  25. data/lib/rubocop/cop/rspec/scattered_let.rb +13 -0
  26. data/lib/rubocop/cop/rspec/scattered_setup.rb +27 -9
  27. data/lib/rubocop/cop/rspec/shared_examples.rb +1 -0
  28. data/lib/rubocop/cop/rspec/subject_stub.rb +25 -47
  29. data/lib/rubocop/cop/rspec/variable_definition.rb +56 -0
  30. data/lib/rubocop/cop/rspec/variable_name.rb +47 -0
  31. data/lib/rubocop/cop/rspec_cops.rb +7 -1
  32. data/lib/rubocop/rspec/corrector/move_node.rb +52 -0
  33. data/lib/rubocop/rspec/description_extractor.rb +2 -6
  34. data/lib/rubocop/rspec/example.rb +1 -1
  35. data/lib/rubocop/rspec/hook.rb +44 -11
  36. data/lib/rubocop/rspec/language.rb +20 -0
  37. data/lib/rubocop/rspec/language/node_pattern.rb +1 -1
  38. data/lib/rubocop/rspec/variable.rb +16 -0
  39. data/lib/rubocop/rspec/version.rb +1 -1
  40. metadata +16 -9
  41. data/lib/rubocop/rspec/util.rb +0 -19
@@ -58,6 +58,13 @@ module RuboCop
58
58
  (block (send (const nil? :Class) :new ...) ...)
59
59
  PATTERN
60
60
 
61
+ def_node_matcher :custom_matcher?, <<-PATTERN
62
+ (block {
63
+ (send nil? :matcher sym)
64
+ (send (const (const nil? :RSpec) :Matchers) :define sym)
65
+ } ...)
66
+ PATTERN
67
+
61
68
  def_node_search :ivar_usage, '$(ivar $_)'
62
69
 
63
70
  def_node_search :ivar_assigned?, '(ivasgn % ...)'
@@ -66,8 +73,8 @@ module RuboCop
66
73
  return unless spec_group?(node)
67
74
 
68
75
  ivar_usage(node) do |ivar, name|
69
- return if inside_dynamic_class?(ivar)
70
- return if assignment_only? && !ivar_assigned?(node, name)
76
+ next if valid_usage?(ivar)
77
+ next if assignment_only? && !ivar_assigned?(node, name)
71
78
 
72
79
  add_offense(ivar)
73
80
  end
@@ -75,8 +82,10 @@ module RuboCop
75
82
 
76
83
  private
77
84
 
78
- def inside_dynamic_class?(node)
79
- node.each_ancestor(:block).any? { |block| dynamic_class?(block) }
85
+ def valid_usage?(node)
86
+ node.each_ancestor(:block).any? do |block|
87
+ dynamic_class?(block) || custom_matcher?(block)
88
+ end
80
89
  end
81
90
 
82
91
  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)
@@ -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
@@ -11,7 +11,7 @@ module RuboCop
11
11
  #
12
12
  # # better
13
13
  # thing = Thing.new(baz: 42)
14
- # allow(foo).to receive(bar: thing)
14
+ # allow(foo).to receive(:bar).and_return(thing)
15
15
  #
16
16
  class MessageChain < Cop
17
17
  MSG = 'Avoid stubbing using `%<method>s`.'
@@ -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 '\
@@ -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
@@ -35,6 +35,15 @@ module RuboCop
35
35
  check_let_declarations(node.body)
36
36
  end
37
37
 
38
+ def autocorrect(node)
39
+ lambda do |corrector|
40
+ first_let = find_first_let(node.parent)
41
+ RuboCop::RSpec::Corrector::MoveNode.new(
42
+ node, corrector, processed_source
43
+ ).move_after(first_let)
44
+ end
45
+ end
46
+
38
47
  private
39
48
 
40
49
  def check_let_declarations(body)
@@ -47,6 +56,10 @@ module RuboCop
47
56
  add_offense(node)
48
57
  end
49
58
  end
59
+
60
+ def find_first_let(node)
61
+ node.children.find { |child| let?(child) }
62
+ end
50
63
  end
51
64
  end
52
65
  end
@@ -23,25 +23,43 @@ module RuboCop
23
23
  # end
24
24
  #
25
25
  class ScatteredSetup < Cop
26
- MSG = 'Do not define multiple hooks in the same example group.'
26
+ MSG = 'Do not define multiple `%<hook_name>s` hooks in the same '\
27
+ 'example group (also defined on %<lines>s).'
27
28
 
28
29
  def on_block(node)
29
30
  return unless example_group?(node)
30
31
 
31
- analyzable_hooks(node).each do |repeated_hook|
32
- add_offense(repeated_hook)
32
+ repeated_hooks(node).each do |occurrences|
33
+ lines = occurrences.map(&:first_line)
34
+
35
+ occurrences.each do |occurrence|
36
+ lines_except_current = lines - [occurrence.first_line]
37
+ message = format(MSG, hook_name: occurrences.first.method_name,
38
+ lines: lines_msg(lines_except_current))
39
+ add_offense(occurrence, message: message)
40
+ end
33
41
  end
34
42
  end
35
43
 
36
- def analyzable_hooks(node)
37
- RuboCop::RSpec::ExampleGroup.new(node)
44
+ def repeated_hooks(node)
45
+ hooks = RuboCop::RSpec::ExampleGroup.new(node)
38
46
  .hooks
39
- .select { |hook| hook.knowable_scope? && hook.valid_scope? }
40
- .group_by { |hook| [hook.name, hook.scope] }
47
+ .select(&:knowable_scope?)
48
+ .group_by { |hook| [hook.name, hook.scope, hook.metadata] }
41
49
  .values
42
50
  .reject(&:one?)
43
- .flatten
44
- .map(&:to_node)
51
+
52
+ hooks.map do |hook|
53
+ hook.map(&:to_node)
54
+ end
55
+ end
56
+
57
+ def lines_msg(numbers)
58
+ if numbers.size == 1
59
+ "line #{numbers.first}"
60
+ else
61
+ "lines #{numbers.join(', ')}"
62
+ end
45
63
  end
46
64
  end
47
65
  end