rubocop-rspec 1.6.0 → 1.7.0

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