rubocop-rspec 1.12.0 → 1.13.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 (85) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +11 -0
  3. data/config/default.yml +15 -1
  4. data/lib/rubocop-rspec.rb +2 -0
  5. data/lib/rubocop/cop/rspec/any_instance.rb +7 -10
  6. data/lib/rubocop/cop/rspec/around_block.rb +19 -30
  7. data/lib/rubocop/cop/rspec/be_eql.rb +3 -7
  8. data/lib/rubocop/cop/rspec/before_after_all.rb +10 -16
  9. data/lib/rubocop/cop/rspec/cop.rb +5 -1
  10. data/lib/rubocop/cop/rspec/describe_class.rb +8 -8
  11. data/lib/rubocop/cop/rspec/describe_method.rb +6 -5
  12. data/lib/rubocop/cop/rspec/described_class.rb +2 -2
  13. data/lib/rubocop/cop/rspec/example_length.rb +5 -8
  14. data/lib/rubocop/cop/rspec/example_wording.rb +57 -23
  15. data/lib/rubocop/cop/rspec/expect_actual.rb +3 -9
  16. data/lib/rubocop/cop/rspec/expect_output.rb +2 -2
  17. data/lib/rubocop/cop/rspec/file_path.rb +30 -29
  18. data/lib/rubocop/cop/rspec/hook_argument.rb +1 -1
  19. data/lib/rubocop/cop/rspec/instance_spy.rb +12 -12
  20. data/lib/rubocop/cop/rspec/instance_variable.rb +2 -2
  21. data/lib/rubocop/cop/rspec/it_behaves_like.rb +47 -0
  22. data/lib/rubocop/cop/rspec/leading_subject.rb +1 -1
  23. data/lib/rubocop/cop/rspec/message_chain.rb +7 -4
  24. data/lib/rubocop/cop/rspec/message_spies.rb +6 -5
  25. data/lib/rubocop/cop/rspec/multiple_describes.rb +1 -1
  26. data/lib/rubocop/cop/rspec/multiple_expectations.rb +38 -6
  27. data/lib/rubocop/cop/rspec/named_subject.rb +2 -2
  28. data/lib/rubocop/cop/rspec/nested_groups.rb +10 -6
  29. data/lib/rubocop/cop/rspec/not_to_not.rb +12 -23
  30. data/lib/rubocop/cop/rspec/shared_context.rb +107 -0
  31. data/lib/rubocop/cop/rspec/single_argument_message_chain.rb +16 -23
  32. data/lib/rubocop/cop/rspec/subject_stub.rb +4 -4
  33. data/lib/rubocop/cop/rspec/verified_doubles.rb +4 -3
  34. data/lib/rubocop/rspec/example_group.rb +1 -1
  35. data/lib/rubocop/rspec/language.rb +25 -7
  36. data/lib/rubocop/rspec/version.rb +1 -1
  37. data/spec/expect_violation/expectation_spec.rb +16 -16
  38. data/spec/project/changelog_spec.rb +1 -1
  39. data/spec/project/default_config_spec.rb +1 -1
  40. data/spec/project/project_requires_spec.rb +1 -1
  41. data/spec/rubocop/cop/rspec/any_instance_spec.rb +4 -4
  42. data/spec/rubocop/cop/rspec/around_block_spec.rb +115 -26
  43. data/spec/rubocop/cop/rspec/be_eql_spec.rb +9 -9
  44. data/spec/rubocop/cop/rspec/before_after_all_spec.rb +38 -80
  45. data/spec/rubocop/cop/rspec/describe_class_spec.rb +1 -1
  46. data/spec/rubocop/cop/rspec/describe_method_spec.rb +2 -2
  47. data/spec/rubocop/cop/rspec/described_class_spec.rb +13 -13
  48. data/spec/rubocop/cop/rspec/empty_example_group_spec.rb +1 -1
  49. data/spec/rubocop/cop/rspec/example_length_spec.rb +3 -32
  50. data/spec/rubocop/cop/rspec/example_wording_spec.rb +21 -2
  51. data/spec/rubocop/cop/rspec/expect_actual_spec.rb +33 -18
  52. data/spec/rubocop/cop/rspec/expect_output_spec.rb +3 -3
  53. data/spec/rubocop/cop/rspec/file_path_spec.rb +119 -170
  54. data/spec/rubocop/cop/rspec/focus_spec.rb +1 -1
  55. data/spec/rubocop/cop/rspec/hook_argument_spec.rb +1 -3
  56. data/spec/rubocop/cop/rspec/implicit_expect_spec.rb +1 -1
  57. data/spec/rubocop/cop/rspec/instance_spy_spec.rb +11 -11
  58. data/spec/rubocop/cop/rspec/instance_variable_spec.rb +4 -4
  59. data/spec/rubocop/cop/rspec/it_behaves_like_spec.rb +51 -0
  60. data/spec/rubocop/cop/rspec/leading_subject_spec.rb +1 -1
  61. data/spec/rubocop/cop/rspec/let_setup_spec.rb +1 -1
  62. data/spec/rubocop/cop/rspec/message_chain_spec.rb +3 -3
  63. data/spec/rubocop/cop/rspec/message_expectation_spec.rb +5 -23
  64. data/spec/rubocop/cop/rspec/message_spies_spec.rb +9 -23
  65. data/spec/rubocop/cop/rspec/multiple_describes_spec.rb +1 -1
  66. data/spec/rubocop/cop/rspec/multiple_expectations_spec.rb +66 -3
  67. data/spec/rubocop/cop/rspec/nested_groups_spec.rb +4 -4
  68. data/spec/rubocop/cop/rspec/not_to_not_spec.rb +3 -3
  69. data/spec/rubocop/cop/rspec/repeated_description_spec.rb +1 -1
  70. data/spec/rubocop/cop/rspec/repeated_example_spec.rb +1 -1
  71. data/spec/rubocop/cop/rspec/scattered_setup_spec.rb +1 -1
  72. data/spec/rubocop/cop/rspec/shared_context_spec.rb +142 -0
  73. data/spec/rubocop/cop/rspec/single_argument_message_chain_spec.rb +5 -5
  74. data/spec/rubocop/cop/rspec/subject_stub_spec.rb +1 -1
  75. data/spec/rubocop/cop/rspec/verified_doubles_spec.rb +2 -2
  76. data/spec/rubocop/rspec/config_formatter_spec.rb +12 -12
  77. data/spec/rubocop/rspec/description_extractor_spec.rb +23 -23
  78. data/spec/rubocop/rspec/example_group_spec.rb +11 -11
  79. data/spec/rubocop/rspec/example_spec.rb +1 -1
  80. data/spec/rubocop/rspec/language/selector_set_spec.rb +1 -1
  81. data/spec/rubocop/rspec/util/one_spec.rb +1 -1
  82. data/spec/rubocop/rspec/wording_spec.rb +1 -1
  83. data/spec/shared/detects_style_behavior.rb +3 -4
  84. data/spec/spec_helper.rb +10 -0
  85. metadata +8 -2
@@ -17,7 +17,7 @@ module RuboCop
17
17
  # expect(name).to eq("John")
18
18
  #
19
19
  class ExpectActual < Cop
20
- MSG = 'Provide the actual you are testing to `expect(...)`'.freeze
20
+ MSG = 'Provide the actual you are testing to `expect(...)`.'.freeze
21
21
 
22
22
  SIMPLE_LITERALS = %i(
23
23
  true
@@ -41,7 +41,7 @@ module RuboCop
41
41
  regexp
42
42
  ).freeze
43
43
 
44
- def_node_matcher :expect, '(send _ :expect $_)'
44
+ def_node_matcher :expect_literal, '(send _ :expect $#literal?)'
45
45
 
46
46
  def on_send(node)
47
47
  expect_literal(node) do |argument|
@@ -53,14 +53,8 @@ module RuboCop
53
53
 
54
54
  # This is not implement using a NodePattern because it seems
55
55
  # to not be able to match against an explicit (nil) sexp
56
- def expect_literal(node)
57
- return unless (argument = expect(node))
58
-
59
- yield(argument) if literal?(argument)
60
- end
61
-
62
56
  def literal?(node)
63
- simple_literal?(node) || complex_literal?(node)
57
+ node && (simple_literal?(node) || complex_literal?(node))
64
58
  end
65
59
 
66
60
  def simple_literal?(node)
@@ -15,8 +15,8 @@ module RuboCop
15
15
  # # good
16
16
  # expect { my_app.print_report }.to output('Hello World').to_stdout
17
17
  class ExpectOutput < Cop
18
- MSG = 'Use `expect { ... }.to output(...).to_%<name>s` ' \
19
- 'instead of mutating $%<name>s'.freeze
18
+ MSG = 'Use `expect { ... }.to output(...).to_%<name>s` '\
19
+ 'instead of mutating $%<name>s.'.freeze
20
20
 
21
21
  def_node_matcher :hook?, Hooks::ALL.block_pattern
22
22
 
@@ -44,66 +44,67 @@ module RuboCop
44
44
  class FilePath < Cop
45
45
  include RuboCop::RSpec::TopLevelDescribe
46
46
 
47
- MESSAGE = 'Spec path should end with `%s`'.freeze
48
- ROUTING_PAIR = s(:pair, s(:sym, :type), s(:sym, :routing))
47
+ MSG = 'Spec path should end with `%s`.'.freeze
48
+
49
+ def_node_search :const_described?, '(send _ :describe (const ...) ...)'
50
+ def_node_search :routing_metadata?, '(pair (sym :type) (sym :routing))'
49
51
 
50
52
  def on_top_level_describe(node, args)
53
+ return unless const_described?(node) && single_top_level_describe?
51
54
  return if routing_spec?(args)
52
55
 
53
- return unless single_top_level_describe?
54
- object = args.first.const_name
55
- return unless object
56
+ glob = glob_for(args)
56
57
 
57
- path_matcher = matcher(object, args.at(1))
58
- return if source_filename =~ regexp_from_glob(path_matcher)
58
+ return if filename_ends_with?(glob)
59
59
 
60
- add_offense(node, :expression, format(MESSAGE, path_matcher))
60
+ add_offense(node, :expression, format(MSG, glob))
61
61
  end
62
62
 
63
63
  private
64
64
 
65
65
  def routing_spec?(args)
66
- args.any? do |arg|
67
- arg.children.include?(ROUTING_PAIR)
68
- end
66
+ args.any?(&method(:routing_metadata?))
69
67
  end
70
68
 
71
- def matcher(object, method)
72
- path = File.join(parts(object))
73
- if method && method.type.equal?(:str) && !ignore_methods?
74
- path += '*' + method.str_content.gsub(/\W+/, '')
75
- end
76
-
77
- "#{path}*_spec.rb"
69
+ def glob_for((described_class, method_name))
70
+ "#{expected_path(described_class)}#{name_glob(method_name)}*_spec.rb"
78
71
  end
79
72
 
80
- def parts(object)
81
- object.split('::').map do |p|
82
- custom_transform[p] || camel_to_underscore(p)
83
- end
73
+ def name_glob(name)
74
+ return unless name && name.str_type?
75
+
76
+ "*#{name.str_content.gsub(/\W/, '')}" unless ignore_methods?
84
77
  end
85
78
 
86
- def source_filename
87
- processed_source.buffer.name
79
+ def expected_path(constant)
80
+ File.join(
81
+ constant.const_name.split('::').map do |name|
82
+ custom_transform.fetch(name) { camel_to_snake_case(name) }
83
+ end
84
+ )
88
85
  end
89
86
 
90
- def camel_to_underscore(string)
87
+ def camel_to_snake_case(string)
91
88
  string
92
89
  .gsub(/([^A-Z])([A-Z]+)/, '\1_\2')
93
90
  .gsub(/([A-Z])([A-Z][^A-Z\d]+)/, '\1_\2')
94
91
  .downcase
95
92
  end
96
93
 
97
- def regexp_from_glob(glob)
98
- Regexp.new(glob.sub('.', '\\.').gsub('*', '.*') + '$')
94
+ def custom_transform
95
+ cop_config.fetch('CustomTransform', {})
99
96
  end
100
97
 
101
98
  def ignore_methods?
102
99
  cop_config['IgnoreMethods']
103
100
  end
104
101
 
105
- def custom_transform
106
- cop_config['CustomTransform'] || {}
102
+ def filename_ends_with?(glob)
103
+ File.fnmatch?("*#{glob}", processed_source.buffer.name)
104
+ end
105
+
106
+ def relevant_rubocop_rspec_file?(_)
107
+ true
107
108
  end
108
109
  end
109
110
  end
@@ -66,7 +66,7 @@ module RuboCop
66
66
  HOOKS = Hooks::ALL.node_pattern_union.freeze
67
67
 
68
68
  def_node_matcher :scoped_hook, <<-PATTERN
69
- (block $(send nil #{HOOKS} (sym ${:each :example})) ...)
69
+ (block $(send nil #{HOOKS} (sym ${:each :example})) ...)
70
70
  PATTERN
71
71
 
72
72
  def_node_matcher :unscoped_hook, "(block $(send nil #{HOOKS}) ...)"
@@ -19,27 +19,27 @@ module RuboCop
19
19
  # end
20
20
  #
21
21
  class InstanceSpy < Cop
22
- MSG = 'Use `instance_spy` when you check your double ' \
23
- 'with `have_received`'.freeze
22
+ MSG = 'Use `instance_spy` when you check your double '\
23
+ 'with `have_received`.'.freeze
24
24
 
25
25
  EXAMPLES = Examples::ALL.node_pattern_union.freeze
26
26
 
27
27
  def_node_matcher :example?, "(block $(send nil #{EXAMPLES}) ...)"
28
28
 
29
29
  def_node_search :null_double, <<-PATTERN
30
- (lvasgn $_
31
- (send
32
- $(send nil :instance_double
33
- ...) :as_null_object))
30
+ (lvasgn $_
31
+ (send
32
+ $(send nil :instance_double
33
+ ...) :as_null_object))
34
34
  PATTERN
35
35
 
36
36
  def_node_search :have_received_usage, <<-PATTERN
37
- (send
38
- (send nil :expect
39
- (lvar $_)) :to
40
- (send nil :have_received
37
+ (send
38
+ (send nil :expect
39
+ (lvar $_)) :to
40
+ (send nil :have_received
41
+ ...)
41
42
  ...)
42
- ...)
43
43
  PATTERN
44
44
 
45
45
  def on_block(node)
@@ -47,7 +47,7 @@ module RuboCop
47
47
 
48
48
  null_double(node) do |var, receiver|
49
49
  have_received_usage(node) do |expected|
50
- add_offense(receiver, :expression, MSG) if expected == var
50
+ add_offense(receiver, :expression) if expected == var
51
51
  end
52
52
  end
53
53
  end
@@ -47,7 +47,7 @@ module RuboCop
47
47
  # end
48
48
  #
49
49
  class InstanceVariable < Cop
50
- MESSAGE = 'Use `let` instead of an instance variable'.freeze
50
+ MSG = 'Use `let` instead of an instance variable.'.freeze
51
51
 
52
52
  EXAMPLE_GROUP_METHODS = ExampleGroups::ALL + SharedGroups::ALL
53
53
 
@@ -63,7 +63,7 @@ module RuboCop
63
63
  ivar_usage(node) do |ivar, name|
64
64
  return if assignment_only? && !ivar_assigned?(node, name)
65
65
 
66
- add_offense(ivar, :expression, MESSAGE)
66
+ add_offense(ivar, :expression)
67
67
  end
68
68
  end
69
69
 
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module RSpec
6
+ # Checks that only one `it_behaves_like` style is used.
7
+ #
8
+ # @example when configuration is `EnforcedStyle: it_behaves_like`
9
+ # # bad
10
+ # it_should_behave_like 'a foo'
11
+ #
12
+ # # good
13
+ # it_behaves_like 'a foo'
14
+ #
15
+ # @example when configuration is `EnforcedStyle: it_should_behave_like`
16
+ # # bad
17
+ # it_behaves_like 'a foo'
18
+ #
19
+ # # good
20
+ # it_should_behave_like 'a foo'
21
+ class ItBehavesLike < Cop
22
+ include ConfigurableEnforcedStyle
23
+
24
+ MSG = 'Prefer `%s` over `%s` when including examples in '\
25
+ 'a nested context.'.freeze
26
+
27
+ def_node_matcher :example_inclusion_offense, '(send _ % ...)'
28
+
29
+ def on_send(node)
30
+ example_inclusion_offense(node, alternative_style) do
31
+ add_offense(node, :expression)
32
+ end
33
+ end
34
+
35
+ def autocorrect(node)
36
+ ->(corrector) { corrector.replace(node.loc.selector, style.to_s) }
37
+ end
38
+
39
+ private
40
+
41
+ def message(_node)
42
+ format(MSG, style, alternative_style)
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
@@ -27,7 +27,7 @@ module RuboCop
27
27
  # end
28
28
  # end
29
29
  class LeadingSubject < Cop
30
- MSG = 'Declare `subject` above any other `let` declarations'.freeze
30
+ MSG = 'Declare `subject` above any other `let` declarations.'.freeze
31
31
 
32
32
  def_node_matcher :subject?, '(block $(send nil :subject ...) args ...)'
33
33
 
@@ -12,13 +12,16 @@ module RuboCop
12
12
  # allow(foo).to receive(bar: thing)
13
13
  #
14
14
  class MessageChain < Cop
15
- MESSAGE = 'Avoid stubbing using `%<method>s`'.freeze
15
+ MSG = 'Avoid stubbing using `%<method>s`.'.freeze
16
+
17
+ def_node_matcher :message_chain, Matchers::MESSAGE_CHAIN.send_pattern
16
18
 
17
19
  def on_send(node)
18
- _receiver, method_name, *_args = *node
19
- return unless Matchers::MESSAGE_CHAIN.include?(method_name)
20
+ message_chain(node) { add_offense(node, :selector) }
21
+ end
20
22
 
21
- add_offense(node, :selector, format(MESSAGE, method: method_name))
23
+ def message(node)
24
+ format(MSG, method: node.method_name)
22
25
  end
23
26
  end
24
27
  end
@@ -27,11 +27,12 @@ module RuboCop
27
27
  class MessageSpies < Cop
28
28
  include ConfigurableEnforcedStyle
29
29
 
30
- MSG_RECEIVE = 'Prefer `receive` for setting message ' \
31
- 'expectations.'.freeze
32
- MSG_HAVE_RECEIVED = 'Prefer `have_received` for setting message ' \
33
- 'expectations. Setup `%s` as a spy using `allow` or `instance_spy`.'
34
- .freeze
30
+ MSG_RECEIVE = 'Prefer `receive` for setting message '\
31
+ 'expectations.'.freeze
32
+
33
+ MSG_HAVE_RECEIVED = 'Prefer `have_received` for setting message '\
34
+ 'expectations. Setup `%s` as a spy using `allow`'\
35
+ ' or `instance_spy`.'.freeze
35
36
 
36
37
  SUPPORTED_STYLES = %w(have_received receive).freeze
37
38
 
@@ -25,7 +25,7 @@ module RuboCop
25
25
  class MultipleDescribes < Cop
26
26
  include RuboCop::RSpec::TopLevelDescribe
27
27
 
28
- MSG = 'Do not use multiple top level describes - ' \
28
+ MSG = 'Do not use multiple top level describes - '\
29
29
  'try to nest them.'.freeze
30
30
 
31
31
  def on_top_level_describe(node, _args)
@@ -48,22 +48,54 @@ module RuboCop
48
48
  class MultipleExpectations < Cop
49
49
  include ConfigurableMax
50
50
 
51
- MSG = 'Example has too many expectations [%{total}/%{max}]'.freeze
51
+ MSG = 'Example has too many expectations [%{total}/%{max}].'.freeze
52
52
 
53
- def_node_search :expect, '(send _ :expect ...)'
53
+ def_node_search :with_aggregated_failures?, '(sym :aggregate_failures)'
54
+ def_node_search :disabled_aggregated_failures?, <<-PATTERN
55
+ (pair (sym :aggregate_failures) (false))
56
+ PATTERN
57
+
58
+ def_node_matcher :expect?, '(send _ :expect ...)'
59
+ def_node_matcher :aggregate_failures?, <<-PATTERN
60
+ (block (send _ :aggregate_failures ...) ...)
61
+ PATTERN
54
62
 
55
63
  def on_block(node)
56
- return unless example?(node) && (expectations = expect(node))
64
+ return unless example?(node)
65
+
66
+ return if example_with_aggregated_failures?(node)
67
+
68
+ expectations_count = to_enum(:find_expectation, node).count
57
69
 
58
- return if expectations.count <= max_expectations
70
+ return if expectations_count <= max_expectations
59
71
 
60
- self.max = expectations.count
72
+ self.max = expectations_count
61
73
 
62
- flag_example(node, expectation_count: expectations.count)
74
+ flag_example(node, expectation_count: expectations_count)
63
75
  end
64
76
 
65
77
  private
66
78
 
79
+ def example_with_aggregated_failures?(node)
80
+ example = node.children.first
81
+
82
+ with_aggregated_failures?(example) &&
83
+ !disabled_aggregated_failures?(example)
84
+ end
85
+
86
+ def find_expectation(node, &block)
87
+ return unless node.is_a?(Parser::AST::Node)
88
+
89
+ yield if expect?(node) || aggregate_failures?(node)
90
+
91
+ # do not search inside of aggregate_failures block
92
+ return if aggregate_failures?(node)
93
+
94
+ node.children.each do |child|
95
+ find_expectation(child, &block)
96
+ end
97
+ end
98
+
67
99
  def flag_example(node, expectation_count:)
68
100
  method, = *node
69
101
 
@@ -38,8 +38,8 @@ module RuboCop
38
38
  # it { should be_valid }
39
39
  # end
40
40
  class NamedSubject < Cop
41
- MSG = 'Name your test subject if '\
42
- 'you need to reference it explicitly.'.freeze
41
+ MSG = 'Name your test subject if you need '\
42
+ 'to reference it explicitly.'.freeze
43
43
 
44
44
  def_node_matcher :rspec_block?, <<-PATTERN
45
45
  (block
@@ -5,7 +5,7 @@ module RuboCop
5
5
  module RSpec
6
6
  # Checks for nested example groups.
7
7
  #
8
- # This cop is configurable using the `MaxNesting` option
8
+ # This cop is configurable using the `Max` option
9
9
  #
10
10
  # @example
11
11
  # # bad
@@ -56,7 +56,7 @@ module RuboCop
56
56
  #
57
57
  # # .rubocop.yml
58
58
  # RSpec/NestedGroups:
59
- # MaxNesting: 2
59
+ # Max: 2
60
60
  #
61
61
  # context 'when using some feature' do
62
62
  # let(:some) { :various }
@@ -87,7 +87,7 @@ module RuboCop
87
87
  class NestedGroups < Cop
88
88
  include RuboCop::RSpec::TopLevelDescribe
89
89
 
90
- MSG = 'Maximum example group nesting exceeded'.freeze
90
+ MSG = 'Maximum example group nesting exceeded [%d/%d].'.freeze
91
91
 
92
92
  DEPRECATED_MAX_KEY = 'MaxNesting'.freeze
93
93
 
@@ -98,8 +98,8 @@ module RuboCop
98
98
  def_node_search :find_contexts, ExampleGroups::ALL.block_pattern
99
99
 
100
100
  def on_top_level_describe(node, _)
101
- find_nested_contexts(node.parent) do |context|
102
- add_offense(context.children.first, :expression)
101
+ find_nested_contexts(node.parent) do |context, nesting|
102
+ add_offense(context.children.first, :expression, message(nesting))
103
103
  end
104
104
  end
105
105
 
@@ -107,7 +107,7 @@ module RuboCop
107
107
 
108
108
  def find_nested_contexts(node, nesting: 1, &block)
109
109
  find_contexts(node) do |nested_context|
110
- yield(nested_context) if nesting > max_nesting
110
+ yield(nested_context, nesting) if nesting > max_nesting
111
111
 
112
112
  nested_context.each_child_node do |child|
113
113
  find_nested_contexts(child, nesting: nesting + 1, &block)
@@ -115,6 +115,10 @@ module RuboCop
115
115
  end
116
116
  end
117
117
 
118
+ def message(nesting)
119
+ format(MSG, nesting, max_nesting)
120
+ end
121
+
118
122
  def max_nesting
119
123
  @max_nesting ||= Integer(max_nesting_config)
120
124
  end