rubocop-rspec 1.6.0 → 1.7.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 (69) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +22 -0
  3. data/README.md +23 -0
  4. data/Rakefile +19 -1
  5. data/config/default.yml +78 -15
  6. data/lib/rubocop-rspec.rb +19 -1
  7. data/lib/rubocop/cop/rspec/any_instance.rb +5 -1
  8. data/lib/rubocop/cop/rspec/be_eql.rb +58 -0
  9. data/lib/rubocop/cop/rspec/describe_class.rb +2 -3
  10. data/lib/rubocop/cop/rspec/describe_method.rb +4 -3
  11. data/lib/rubocop/cop/rspec/described_class.rb +4 -35
  12. data/lib/rubocop/cop/rspec/empty_example_group.rb +100 -0
  13. data/lib/rubocop/cop/rspec/example_length.rb +5 -2
  14. data/lib/rubocop/cop/rspec/example_wording.rb +5 -2
  15. data/lib/rubocop/cop/rspec/expect_actual.rb +79 -0
  16. data/lib/rubocop/cop/rspec/file_path.rb +3 -1
  17. data/lib/rubocop/cop/rspec/focus.rb +12 -28
  18. data/lib/rubocop/cop/rspec/hook_argument.rb +122 -0
  19. data/lib/rubocop/cop/rspec/instance_variable.rb +53 -12
  20. data/lib/rubocop/cop/rspec/leading_subject.rb +58 -0
  21. data/lib/rubocop/cop/rspec/let_setup.rb +58 -0
  22. data/lib/rubocop/cop/rspec/message_chain.rb +33 -0
  23. data/lib/rubocop/cop/rspec/message_expectation.rb +58 -0
  24. data/lib/rubocop/cop/rspec/multiple_describes.rb +10 -7
  25. data/lib/rubocop/cop/rspec/multiple_expectations.rb +89 -0
  26. data/lib/rubocop/cop/rspec/named_subject.rb +10 -1
  27. data/lib/rubocop/cop/rspec/nested_groups.rb +125 -0
  28. data/lib/rubocop/cop/rspec/not_to_not.rb +3 -3
  29. data/lib/rubocop/cop/rspec/subject_stub.rb +136 -0
  30. data/lib/rubocop/cop/rspec/verified_doubles.rb +4 -1
  31. data/lib/rubocop/rspec.rb +10 -0
  32. data/lib/rubocop/rspec/config_formatter.rb +33 -0
  33. data/lib/rubocop/rspec/description_extractor.rb +35 -0
  34. data/lib/rubocop/rspec/inject.rb +2 -6
  35. data/lib/rubocop/rspec/language.rb +73 -0
  36. data/lib/rubocop/rspec/language/node_pattern.rb +16 -0
  37. data/lib/rubocop/rspec/spec_only.rb +61 -0
  38. data/lib/rubocop/rspec/top_level_describe.rb +6 -0
  39. data/lib/rubocop/rspec/version.rb +1 -1
  40. data/rubocop-rspec.gemspec +1 -0
  41. data/spec/project/changelog_spec.rb +81 -0
  42. data/spec/project/default_config_spec.rb +52 -0
  43. data/spec/project/project_requires_spec.rb +8 -0
  44. data/spec/rubocop/cop/rspec/be_eql_spec.rb +59 -0
  45. data/spec/rubocop/cop/rspec/described_class_spec.rb +2 -2
  46. data/spec/rubocop/cop/rspec/empty_example_group_spec.rb +79 -0
  47. data/spec/rubocop/cop/rspec/example_length_spec.rb +50 -30
  48. data/spec/rubocop/cop/rspec/example_wording_spec.rb +21 -3
  49. data/spec/rubocop/cop/rspec/expect_actual_spec.rb +136 -0
  50. data/spec/rubocop/cop/rspec/file_path_spec.rb +48 -71
  51. data/spec/rubocop/cop/rspec/focus_spec.rb +1 -1
  52. data/spec/rubocop/cop/rspec/hook_argument_spec.rb +189 -0
  53. data/spec/rubocop/cop/rspec/instance_variable_spec.rb +37 -0
  54. data/spec/rubocop/cop/rspec/leading_subject_spec.rb +54 -0
  55. data/spec/rubocop/cop/rspec/let_setup_spec.rb +66 -0
  56. data/spec/rubocop/cop/rspec/message_chain_spec.rb +21 -0
  57. data/spec/rubocop/cop/rspec/message_expectation_spec.rb +63 -0
  58. data/spec/rubocop/cop/rspec/multiple_expectations_spec.rb +84 -0
  59. data/spec/rubocop/cop/rspec/nested_groups_spec.rb +55 -0
  60. data/spec/rubocop/cop/rspec/not_to_not_spec.rb +12 -2
  61. data/spec/rubocop/cop/rspec/subject_stub_spec.rb +183 -0
  62. data/spec/rubocop/rspec/config_formatter_spec.rb +48 -0
  63. data/spec/rubocop/rspec/description_extractor_spec.rb +35 -0
  64. data/spec/rubocop/rspec/language/selector_set_spec.rb +29 -0
  65. data/spec/rubocop/rspec/spec_only_spec.rb +97 -0
  66. data/spec/shared/rspec_only_cop_behavior.rb +68 -0
  67. data/spec/spec_helper.rb +13 -1
  68. metadata +72 -5
  69. data/spec/project_spec.rb +0 -115
@@ -0,0 +1,100 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module RSpec
6
+ # Checks if an example group does not include any tests.
7
+ #
8
+ # This cop is configurable using the `CustomIncludeMethods` option
9
+ #
10
+ # @example usage
11
+ #
12
+ # # bad
13
+ # describe Bacon do
14
+ # let(:bacon) { Bacon.new(chunkiness) }
15
+ # let(:chunkiness) { false }
16
+ #
17
+ # context 'extra chunky' do # flagged by rubocop
18
+ # let(:chunkiness) { true }
19
+ # end
20
+ #
21
+ # it 'is chunky' do
22
+ # expect(bacon.chunky?).to be_truthy
23
+ # end
24
+ # end
25
+ #
26
+ # # good
27
+ # describe Bacon do
28
+ # let(:bacon) { Bacon.new(chunkiness) }
29
+ # let(:chunkiness) { false }
30
+ #
31
+ # it 'is chunky' do
32
+ # expect(bacon.chunky?).to be_truthy
33
+ # end
34
+ # end
35
+ #
36
+ # @example configuration
37
+ #
38
+ # # .rubocop.yml
39
+ # RSpec/EmptyExampleGroup:
40
+ # CustomIncludeMethods:
41
+ # - include_tests
42
+ #
43
+ # # spec_helper.rb
44
+ # RSpec.configure do |config|
45
+ # config.alias_it_behaves_like_to(:include_tests)
46
+ # end
47
+ #
48
+ # # bacon_spec.rb
49
+ # describe Bacon do
50
+ # let(:bacon) { Bacon.new(chunkiness) }
51
+ # let(:chunkiness) { false }
52
+ #
53
+ # context 'extra chunky' do # not flagged by rubocop
54
+ # let(:chunkiness) { true }
55
+ #
56
+ # include_tests 'shared tests'
57
+ # end
58
+ # end
59
+ #
60
+ class EmptyExampleGroup < Cop
61
+ include RuboCop::RSpec::SpecOnly,
62
+ RuboCop::RSpec::Language,
63
+ RuboCop::RSpec::Language::NodePattern
64
+
65
+ MSG = 'Empty example group detected.'.freeze
66
+
67
+ def_node_search :contains_example?, <<-PATTERN
68
+ {
69
+ (send _ {
70
+ #{Examples::ALL.to_node_pattern}
71
+ :it_behaves_like
72
+ :it_should_behave_like
73
+ :include_context
74
+ :include_examples
75
+ } ...)
76
+ (send _ #custom_include? ...)
77
+ }
78
+ PATTERN
79
+
80
+ def on_block(node)
81
+ return unless example_group?(node) && !contains_example?(node)
82
+
83
+ add_offense(node.children.first, :expression)
84
+ end
85
+
86
+ private
87
+
88
+ def custom_include?(method_name)
89
+ custom_include_methods.include?(method_name)
90
+ end
91
+
92
+ def custom_include_methods
93
+ cop_config
94
+ .fetch('CustomIncludeMethods', [])
95
+ .map(&:to_sym)
96
+ end
97
+ end
98
+ end
99
+ end
100
+ end
@@ -3,6 +3,8 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module RSpec
6
+ # Checks for long examples.
7
+ #
6
8
  # A long example is usually more difficult to understand. Consider
7
9
  # extracting out some behaviour, e.g. with a `let` block, or a helper
8
10
  # method.
@@ -24,8 +26,9 @@ module RuboCop
24
26
  # expect(result).to be(true)
25
27
  # end
26
28
  class ExampleLength < Cop
27
- include CodeLength
28
- EXAMPLE_BLOCKS = [:it, :specify].freeze
29
+ include RuboCop::RSpec::SpecOnly, CodeLength
30
+
31
+ EXAMPLE_BLOCKS = RuboCop::RSpec::Language::Examples::ALL
29
32
 
30
33
  def on_block(node)
31
34
  method, _args, _body = *node
@@ -3,8 +3,9 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module RSpec
6
- # Do not use should when describing your tests.
7
- # see: http://betterspecs.org/#should
6
+ # Checks that example descriptions do not start with "should".
7
+ #
8
+ # @see http://betterspecs.org/#should
8
9
  #
9
10
  # The autocorrect is experimental - use with care! It can be configured
10
11
  # with CustomTransform (e.g. have => has) and IgnoredWords (e.g. only).
@@ -18,6 +19,8 @@ module RuboCop
18
19
  # it 'finds nothing' do
19
20
  # end
20
21
  class ExampleWording < Cop
22
+ include RuboCop::RSpec::SpecOnly
23
+
21
24
  MSG = 'Do not use should when describing your tests.'.freeze
22
25
 
23
26
  def on_block(node) # rubocop:disable Metrics/AbcSize,Metrics/MethodLength,Metrics/LineLength
@@ -0,0 +1,79 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module RSpec
6
+ # Checks for `expect(...)` calls containing literal values.
7
+ #
8
+ # @example
9
+ # # bad
10
+ # expect(5).to eq(price)
11
+ # expect(/foo/).to eq(pattern)
12
+ # expect("John").to eq(name)
13
+ #
14
+ # # good
15
+ # expect(price).to eq(5)
16
+ # expect(pattern).to eq(/foo/)
17
+ # expect(name).to eq("John")
18
+ #
19
+ class ExpectActual < Cop
20
+ include RuboCop::RSpec::SpecOnly
21
+
22
+ MSG = 'Provide the actual you are testing to `expect(...)`'.freeze
23
+
24
+ SIMPLE_LITERALS = %i(
25
+ true
26
+ false
27
+ nil
28
+ int
29
+ float
30
+ str
31
+ sym
32
+ complex
33
+ rational
34
+ regopt
35
+ ).freeze
36
+
37
+ COMPLEX_LITERALS = %i(
38
+ array
39
+ hash
40
+ pair
41
+ irange
42
+ erange
43
+ regexp
44
+ ).freeze
45
+
46
+ def_node_matcher :expect, '(send _ :expect $_)'
47
+
48
+ def on_send(node)
49
+ expect_literal(node) do |argument|
50
+ add_offense(argument, :expression)
51
+ end
52
+ end
53
+
54
+ private
55
+
56
+ # This is not implement using a NodePattern because it seems
57
+ # to not be able to match against an explicit (nil) sexp
58
+ def expect_literal(node)
59
+ return unless (argument = expect(node))
60
+
61
+ yield(argument) if literal?(argument)
62
+ end
63
+
64
+ def literal?(node)
65
+ simple_literal?(node) || complex_literal?(node)
66
+ end
67
+
68
+ def simple_literal?(node)
69
+ SIMPLE_LITERALS.include?(node.type)
70
+ end
71
+
72
+ def complex_literal?(node)
73
+ COMPLEX_LITERALS.include?(node.type) &&
74
+ node.each_child_node.all?(&method(:literal?))
75
+ end
76
+ end
77
+ end
78
+ end
79
+ end
@@ -3,6 +3,8 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module RSpec
6
+ # Checks that spec file paths are consistent with the test subject.
7
+ #
6
8
  # Checks the path of the spec file and enforces that it reflects the
7
9
  # described class/module and its optionally called out method.
8
10
  #
@@ -15,7 +17,7 @@ module RuboCop
15
17
  # my_class_method_spec.rb # describe MyClass, '#method'
16
18
  # my_class_spec.rb # describe MyClass
17
19
  class FilePath < Cop
18
- include RuboCop::RSpec::TopLevelDescribe
20
+ include RuboCop::RSpec::SpecOnly, RuboCop::RSpec::TopLevelDescribe
19
21
 
20
22
  MESSAGE = 'Spec path should end with `%s`'.freeze
21
23
  METHOD_STRING_MATCHER = /^[\#\.].+/
@@ -3,7 +3,7 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module RSpec
6
- # Checks if test is focused.
6
+ # Checks if examples are focused.
7
7
  #
8
8
  # @example
9
9
  # # bad
@@ -20,36 +20,20 @@ module RuboCop
20
20
  # describe MyClass do
21
21
  # end
22
22
  class Focus < Cop
23
+ include RuboCop::RSpec::SpecOnly, RuboCop::RSpec::Language
24
+
23
25
  MSG = 'Focused spec found.'.freeze
24
26
 
25
- FOCUSABLE_SELECTORS = '
26
- :context
27
- :describe
28
- :example
29
- :example_group
30
- :feature
31
- :it
32
- :scenario
33
- :specify
34
- :xcontext
35
- :xdescribe
36
- :xexample
37
- :xfeature
38
- :xit
39
- :xscenario
40
- :xspecify
41
- '.freeze
27
+ focusable =
28
+ ExampleGroups::GROUPS +
29
+ ExampleGroups::SKIPPED +
30
+ Examples::EXAMPLES +
31
+ Examples::SKIPPED
32
+
33
+ focused = ExampleGroups::FOCUSED + Examples::FOCUSED
42
34
 
43
- FOCUSING_SELECTORS = '
44
- :fcontext
45
- :fdescribe
46
- :fexample
47
- :ffeature
48
- :fit
49
- :focus
50
- :fscenario
51
- :fspecify
52
- '.freeze
35
+ FOCUSABLE_SELECTORS = focusable.to_node_pattern
36
+ FOCUSING_SELECTORS = focused.to_node_pattern
53
37
 
54
38
  FOCUS_SYMBOL = s(:sym, :focus)
55
39
  FOCUS_TRUE = s(:pair, FOCUS_SYMBOL, s(:true))
@@ -0,0 +1,122 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module RSpec
6
+ # Checks the arguments passed to `before`, `around`, and `after`.
7
+ #
8
+ # This cop checks for consistent style when specifying RSpec
9
+ # hooks which run for each example. There are three supported
10
+ # styles: "implicit", "each", and "example." All styles have
11
+ # the same behavior.
12
+ #
13
+ # @example when configuration is `EnforcedStyle: implicit`
14
+ # # bad
15
+ # before(:each) do
16
+ # ...
17
+ # end
18
+ #
19
+ # # bad
20
+ # before(:example) do
21
+ # ...
22
+ # end
23
+ #
24
+ # # good
25
+ # before do
26
+ # ...
27
+ # end
28
+ #
29
+ # @example when configuration is `EnforcedStyle: each`
30
+ # # bad
31
+ # before(:example) do
32
+ # ...
33
+ # end
34
+ #
35
+ # # good
36
+ # before do
37
+ # ...
38
+ # end
39
+ #
40
+ # # good
41
+ # before(:each) do
42
+ # ...
43
+ # end
44
+ #
45
+ # @example when configuration is `EnforcedStyle: example`
46
+ # # bad
47
+ # before(:each) do
48
+ # ...
49
+ # end
50
+ #
51
+ # # bad
52
+ # before do
53
+ # ...
54
+ # end
55
+ #
56
+ # # good
57
+ # before(:example) do
58
+ # ...
59
+ # end
60
+ class HookArgument < RuboCop::Cop::Cop
61
+ include RuboCop::RSpec::Language,
62
+ RuboCop::RSpec::SpecOnly,
63
+ ConfigurableEnforcedStyle
64
+
65
+ IMPLICIT_MSG = 'Omit the default `%p` argument for RSpec hooks.'.freeze
66
+ EXPLICIT_MSG = 'Use `%p` for RSpec hooks.'.freeze
67
+
68
+ HOOKS = "{#{Hooks::ALL.to_node_pattern}}".freeze
69
+
70
+ def_node_matcher :scoped_hook, <<-PATTERN
71
+ (block $(send nil #{HOOKS} (sym ${:each :example})) ...)
72
+ PATTERN
73
+
74
+ def_node_matcher :unscoped_hook, "(block $(send nil #{HOOKS}) ...)"
75
+
76
+ def on_block(node)
77
+ hook(node) do |method_send, scope_name|
78
+ return correct_style_detected if scope_name.equal?(style)
79
+ return check_implicit(method_send) unless scope_name
80
+
81
+ style_detected(scope_name)
82
+ add_offense(method_send, :expression, explicit_message(scope_name))
83
+ end
84
+ end
85
+
86
+ def autocorrect(node)
87
+ scope = "(#{style.inspect})" unless implicit_style?
88
+ hook = "#{node.method_name}#{scope}"
89
+
90
+ lambda do |corrector|
91
+ corrector.replace(node.loc.expression, hook)
92
+ end
93
+ end
94
+
95
+ private
96
+
97
+ def check_implicit(method_send)
98
+ style_detected(:implicit)
99
+ return if implicit_style?
100
+
101
+ add_offense(method_send, :selector, format(EXPLICIT_MSG, style))
102
+ end
103
+
104
+ def explicit_message(scope)
105
+ if implicit_style?
106
+ format(IMPLICIT_MSG, scope)
107
+ else
108
+ format(EXPLICIT_MSG, style)
109
+ end
110
+ end
111
+
112
+ def implicit_style?
113
+ style.equal?(:implicit)
114
+ end
115
+
116
+ def hook(node, &block)
117
+ scoped_hook(node, &block) || unscoped_hook(node, &block)
118
+ end
119
+ end
120
+ end
121
+ end
122
+ end
@@ -3,8 +3,12 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module RSpec
6
- # When you have to assign a variable instead of using an instance
7
- # variable, use let.
6
+ # Checks for instance variable usage in specs.
7
+ #
8
+ # This cop can be configured with the option `AssignmentOnly` which
9
+ # will configure the cop to only register offenses on instance
10
+ # variable usage if the instance variable is also assigned within
11
+ # the spec
8
12
  #
9
13
  # @example
10
14
  # # bad
@@ -18,22 +22,59 @@ module RuboCop
18
22
  # let(:foo) { [] }
19
23
  # it { expect(foo).to be_empty }
20
24
  # end
25
+ #
26
+ # @example with AssignmentOnly configuration
27
+ #
28
+ # # rubocop.yml
29
+ # RSpec/InstanceVariable:
30
+ # AssignmentOnly: false
31
+ #
32
+ # # bad
33
+ # describe MyClass do
34
+ # before { @foo = [] }
35
+ # it { expect(@foo).to be_empty }
36
+ # end
37
+ #
38
+ # # allowed
39
+ # describe MyClass do
40
+ # it { expect(@foo).to be_empty }
41
+ # end
42
+ #
43
+ # # good
44
+ # describe MyClass do
45
+ # let(:foo) { [] }
46
+ # it { expect(foo).to be_empty }
47
+ # end
48
+ #
21
49
  class InstanceVariable < Cop
50
+ include RuboCop::RSpec::SpecOnly, RuboCop::RSpec::Language
51
+
22
52
  MESSAGE = 'Use `let` instead of an instance variable'.freeze
23
- EXAMPLE_GROUP_METHODS = [
24
- :example_group, :describe, :context, :xdescribe, :xcontext,
25
- :fdescribe, :fcontext, :shared_examples, :shared_context,
26
- :share_examples_for, :shared_examples_for, :feature
27
- ].freeze
53
+
54
+ EXAMPLE_GROUP_METHODS = ExampleGroups::ALL + SharedGroups::ALL
55
+
56
+ def_node_matcher :spec_group?, <<-PATTERN
57
+ (block (send _ {#{EXAMPLE_GROUP_METHODS.to_node_pattern}} ...) ...)
58
+ PATTERN
59
+
60
+ def_node_search :ivar_usage, '$(ivar $_)'
61
+
62
+ def_node_search :ivar_assigned?, '(ivasgn % ...)'
28
63
 
29
64
  def on_block(node)
30
- method, _args, _body = *node
31
- _receiver, method_name, _object = *method
32
- @in_spec = true if EXAMPLE_GROUP_METHODS.include?(method_name)
65
+ return unless spec_group?(node)
66
+
67
+ ivar_usage(node) do |ivar, name|
68
+ return if assignment_only? && !ivar_assigned?(node, name)
69
+
70
+ add_offense(ivar, :expression, MESSAGE)
71
+ end
33
72
  end
34
73
 
35
- def on_ivar(node)
36
- add_offense(node, :expression, MESSAGE) if @in_spec
74
+ private
75
+
76
+ def assignment_only?
77
+ cop_config['AssignmentOnly']
37
78
  end
38
79
  end
39
80
  end