rubocop-rspec 1.12.0 → 1.13.0

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