rubocop-rspec 2.21.0 → 3.0.1

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 (123) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +147 -9
  3. data/README.md +2 -2
  4. data/config/default.yml +159 -244
  5. data/config/obsoletion.yml +24 -0
  6. data/lib/rubocop/cop/rspec/around_block.rb +3 -3
  7. data/lib/rubocop/cop/rspec/base.rb +0 -1
  8. data/lib/rubocop/cop/rspec/be.rb +1 -1
  9. data/lib/rubocop/cop/rspec/be_empty.rb +1 -0
  10. data/lib/rubocop/cop/rspec/be_eq.rb +1 -1
  11. data/lib/rubocop/cop/rspec/be_eql.rb +1 -1
  12. data/lib/rubocop/cop/rspec/be_nil.rb +2 -2
  13. data/lib/rubocop/cop/rspec/before_after_all.rb +7 -13
  14. data/lib/rubocop/cop/rspec/change_by_zero.rb +30 -4
  15. data/lib/rubocop/cop/rspec/context_method.rb +2 -2
  16. data/lib/rubocop/cop/rspec/context_wording.rb +1 -1
  17. data/lib/rubocop/cop/rspec/describe_symbol.rb +1 -1
  18. data/lib/rubocop/cop/rspec/described_class.rb +33 -11
  19. data/lib/rubocop/cop/rspec/dialect.rb +13 -0
  20. data/lib/rubocop/cop/rspec/duplicated_metadata.rb +1 -1
  21. data/lib/rubocop/cop/rspec/empty_example_group.rb +4 -1
  22. data/lib/rubocop/cop/rspec/empty_hook.rb +1 -1
  23. data/lib/rubocop/cop/rspec/empty_line_after_example.rb +2 -2
  24. data/lib/rubocop/cop/rspec/empty_metadata.rb +46 -0
  25. data/lib/rubocop/cop/rspec/empty_output.rb +47 -0
  26. data/lib/rubocop/cop/rspec/eq.rb +47 -0
  27. data/lib/rubocop/cop/rspec/example_length.rb +11 -5
  28. data/lib/rubocop/cop/rspec/example_without_description.rb +11 -2
  29. data/lib/rubocop/cop/rspec/example_wording.rb +11 -2
  30. data/lib/rubocop/cop/rspec/excessive_docstring_spacing.rb +14 -5
  31. data/lib/rubocop/cop/rspec/expect_actual.rb +17 -14
  32. data/lib/rubocop/cop/rspec/expect_change.rb +2 -2
  33. data/lib/rubocop/cop/rspec/expect_in_hook.rb +1 -1
  34. data/lib/rubocop/cop/rspec/expect_in_let.rb +42 -0
  35. data/lib/rubocop/cop/rspec/expect_output.rb +1 -4
  36. data/lib/rubocop/cop/rspec/focus.rb +17 -2
  37. data/lib/rubocop/cop/rspec/hook_argument.rb +2 -2
  38. data/lib/rubocop/cop/rspec/hooks_before_examples.rb +1 -1
  39. data/lib/rubocop/cop/rspec/implicit_block_expectation.rb +2 -2
  40. data/lib/rubocop/cop/rspec/implicit_expect.rb +1 -1
  41. data/lib/rubocop/cop/rspec/implicit_subject.rb +2 -2
  42. data/lib/rubocop/cop/rspec/indexed_let.rb +32 -1
  43. data/lib/rubocop/cop/rspec/instance_spy.rb +2 -2
  44. data/lib/rubocop/cop/rspec/instance_variable.rb +4 -4
  45. data/lib/rubocop/cop/rspec/is_expected_specify.rb +45 -0
  46. data/lib/rubocop/cop/rspec/iterated_expectation.rb +3 -3
  47. data/lib/rubocop/cop/rspec/leaky_constant_declaration.rb +2 -2
  48. data/lib/rubocop/cop/rspec/let_before_examples.rb +5 -1
  49. data/lib/rubocop/cop/rspec/let_setup.rb +1 -1
  50. data/lib/rubocop/cop/rspec/message_expectation.rb +1 -2
  51. data/lib/rubocop/cop/rspec/message_spies.rb +0 -2
  52. data/lib/rubocop/cop/rspec/metadata_style.rb +202 -0
  53. data/lib/rubocop/cop/rspec/missing_expectation_target_method.rb +54 -0
  54. data/lib/rubocop/cop/rspec/mixin/file_help.rb +14 -0
  55. data/lib/rubocop/cop/rspec/mixin/metadata.rb +21 -7
  56. data/lib/rubocop/cop/rspec/mixin/skip_or_pending.rb +2 -2
  57. data/lib/rubocop/cop/rspec/multiple_describes.rb +1 -1
  58. data/lib/rubocop/cop/rspec/multiple_expectations.rb +16 -11
  59. data/lib/rubocop/cop/rspec/multiple_memoized_helpers.rb +2 -4
  60. data/lib/rubocop/cop/rspec/named_subject.rb +6 -3
  61. data/lib/rubocop/cop/rspec/pending.rb +12 -2
  62. data/lib/rubocop/cop/rspec/pending_without_reason.rb +1 -1
  63. data/lib/rubocop/cop/rspec/predicate_matcher.rb +10 -10
  64. data/lib/rubocop/cop/rspec/receive_counts.rb +1 -1
  65. data/lib/rubocop/cop/rspec/receive_messages.rb +161 -0
  66. data/lib/rubocop/cop/rspec/redundant_predicate_matcher.rb +67 -0
  67. data/lib/rubocop/cop/rspec/remove_const.rb +39 -0
  68. data/lib/rubocop/cop/rspec/repeated_example.rb +6 -6
  69. data/lib/rubocop/cop/rspec/repeated_example_group_body.rb +1 -1
  70. data/lib/rubocop/cop/rspec/repeated_example_group_description.rb +2 -2
  71. data/lib/rubocop/cop/rspec/repeated_include_example.rb +1 -1
  72. data/lib/rubocop/cop/rspec/repeated_subject_call.rb +125 -0
  73. data/lib/rubocop/cop/rspec/return_from_stub.rb +1 -1
  74. data/lib/rubocop/cop/rspec/scattered_setup.rb +1 -1
  75. data/lib/rubocop/cop/rspec/shared_context.rb +1 -1
  76. data/lib/rubocop/cop/rspec/shared_examples.rb +66 -20
  77. data/lib/rubocop/cop/rspec/single_argument_message_chain.rb +2 -3
  78. data/lib/rubocop/cop/rspec/sort_metadata.rb +3 -2
  79. data/lib/rubocop/cop/rspec/spec_file_path_format.rb +133 -0
  80. data/lib/rubocop/cop/rspec/spec_file_path_suffix.rb +40 -0
  81. data/lib/rubocop/cop/rspec/stubbed_mock.rb +4 -2
  82. data/lib/rubocop/cop/rspec/subject_stub.rb +6 -6
  83. data/lib/rubocop/cop/rspec/undescriptive_literals_description.rb +69 -0
  84. data/lib/rubocop/cop/rspec/unspecified_exception.rb +2 -2
  85. data/lib/rubocop/cop/rspec/variable_definition.rb +4 -4
  86. data/lib/rubocop/cop/rspec/verified_double_reference.rb +6 -6
  87. data/lib/rubocop/cop/rspec/verified_doubles.rb +2 -2
  88. data/lib/rubocop/cop/rspec/void_expect.rb +4 -3
  89. data/lib/rubocop/cop/rspec_cops.rb +14 -28
  90. data/lib/rubocop/rspec/concept.rb +0 -1
  91. data/lib/rubocop/rspec/config_formatter.rb +1 -11
  92. data/lib/rubocop/rspec/cop/generator.rb +25 -0
  93. data/lib/rubocop/rspec/language.rb +8 -9
  94. data/lib/rubocop/rspec/node.rb +1 -1
  95. data/lib/rubocop/rspec/shared_contexts/default_rspec_language_config_context.rb +1 -1
  96. data/lib/rubocop/rspec/version.rb +1 -1
  97. data/lib/rubocop/rspec/wording.rb +8 -0
  98. data/lib/rubocop-rspec.rb +2 -16
  99. metadata +27 -49
  100. data/lib/rubocop/cop/rspec/capybara/current_path_expectation.rb +0 -39
  101. data/lib/rubocop/cop/rspec/capybara/feature_methods.rb +0 -104
  102. data/lib/rubocop/cop/rspec/capybara/match_style.rb +0 -38
  103. data/lib/rubocop/cop/rspec/capybara/negation_matcher.rb +0 -33
  104. data/lib/rubocop/cop/rspec/capybara/specific_actions.rb +0 -29
  105. data/lib/rubocop/cop/rspec/capybara/specific_finders.rb +0 -24
  106. data/lib/rubocop/cop/rspec/capybara/specific_matcher.rb +0 -35
  107. data/lib/rubocop/cop/rspec/capybara/visibility_matcher.rb +0 -36
  108. data/lib/rubocop/cop/rspec/factory_bot/attribute_defined_statically.rb +0 -128
  109. data/lib/rubocop/cop/rspec/factory_bot/consistent_parentheses_style.rb +0 -117
  110. data/lib/rubocop/cop/rspec/factory_bot/create_list.rb +0 -260
  111. data/lib/rubocop/cop/rspec/factory_bot/factory_class_name.rb +0 -56
  112. data/lib/rubocop/cop/rspec/factory_bot/factory_name_style.rb +0 -74
  113. data/lib/rubocop/cop/rspec/factory_bot/syntax_methods.rb +0 -89
  114. data/lib/rubocop/cop/rspec/file_path.rb +0 -173
  115. data/lib/rubocop/cop/rspec/rails/avoid_setup_hook.rb +0 -43
  116. data/lib/rubocop/cop/rspec/rails/have_http_status.rb +0 -55
  117. data/lib/rubocop/cop/rspec/rails/http_status.rb +0 -203
  118. data/lib/rubocop/cop/rspec/rails/inferred_spec_type.rb +0 -145
  119. data/lib/rubocop/cop/rspec/rails/minitest_assertions.rb +0 -60
  120. data/lib/rubocop/cop/rspec/rails/travel_around.rb +0 -92
  121. data/lib/rubocop/rspec/factory_bot/language.rb +0 -37
  122. data/lib/rubocop/rspec/factory_bot.rb +0 -64
  123. data/lib/rubocop/rspec/language/node_pattern.rb +0 -48
@@ -0,0 +1,161 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module RSpec
6
+ # Checks for multiple messages stubbed on the same object.
7
+ #
8
+ # @safety
9
+ # The autocorrection is marked as unsafe, because it may change the
10
+ # order of stubs. This in turn may cause e.g. variables to be called
11
+ # before they are defined.
12
+ #
13
+ # @example
14
+ # # bad
15
+ # before do
16
+ # allow(Service).to receive(:foo).and_return(bar)
17
+ # allow(Service).to receive(:baz).and_return(qux)
18
+ # end
19
+ #
20
+ # # good
21
+ # before do
22
+ # allow(Service).to receive_messages(foo: bar, baz: qux)
23
+ # end
24
+ #
25
+ # # good - ignore same message
26
+ # before do
27
+ # allow(Service).to receive(:foo).and_return(bar)
28
+ # allow(Service).to receive(:foo).and_return(qux)
29
+ # end
30
+ #
31
+ class ReceiveMessages < Base
32
+ extend AutoCorrector
33
+ include RangeHelp
34
+
35
+ MSG = 'Use `receive_messages` instead of multiple stubs on lines ' \
36
+ '%<loc>s.'
37
+
38
+ # @!method allow_receive_message?(node)
39
+ def_node_matcher :allow_receive_message?, <<~PATTERN
40
+ (send (send nil? :allow ...) :to (send (send nil? :receive (sym _)) :and_return !#heredoc_or_splat?))
41
+ PATTERN
42
+
43
+ # @!method allow_argument(node)
44
+ def_node_matcher :allow_argument, <<~PATTERN
45
+ (send (send nil? :allow $_ ...) ...)
46
+ PATTERN
47
+
48
+ # @!method receive_node(node)
49
+ def_node_search :receive_node, <<~PATTERN
50
+ $(send (send nil? :receive ...) ...)
51
+ PATTERN
52
+
53
+ # @!method receive_arg(node)
54
+ def_node_search :receive_arg, <<~PATTERN
55
+ (send (send nil? :receive $_) ...)
56
+ PATTERN
57
+
58
+ # @!method receive_and_return_argument(node)
59
+ def_node_matcher :receive_and_return_argument, <<~PATTERN
60
+ (send (send nil? :allow ...) :to (send (send nil? :receive (sym $_)) :and_return $_))
61
+ PATTERN
62
+
63
+ def on_begin(node)
64
+ repeated_receive_message(node).each do |item, repeated_lines, args|
65
+ next if repeated_lines.empty?
66
+
67
+ register_offense(item, repeated_lines, args)
68
+ end
69
+ end
70
+
71
+ private
72
+
73
+ def repeated_receive_message(node)
74
+ node
75
+ .children
76
+ .select { |child| allow_receive_message?(child) }
77
+ .group_by { |child| allow_argument(child) }
78
+ .values
79
+ .reject(&:one?)
80
+ .flat_map { |items| add_repeated_lines_and_arguments(items) }
81
+ end
82
+
83
+ def add_repeated_lines_and_arguments(items)
84
+ uniq_items = uniq_items(items)
85
+ repeated_lines = uniq_items.map(&:first_line)
86
+ uniq_items.map do |item|
87
+ [item, repeated_lines - [item.first_line], arguments(uniq_items)]
88
+ end
89
+ end
90
+
91
+ def uniq_items(items)
92
+ items.select do |item|
93
+ items.none? do |i|
94
+ receive_arg(item).first == receive_arg(i).first &&
95
+ !same_line?(item, i)
96
+ end
97
+ end
98
+ end
99
+
100
+ def arguments(items)
101
+ items.map do |item|
102
+ receive_and_return_argument(item) do |receive_arg, return_arg|
103
+ "#{normalize_receive_arg(receive_arg)}: " \
104
+ "#{normalize_return_arg(return_arg)}"
105
+ end
106
+ end
107
+ end
108
+
109
+ def normalize_receive_arg(receive_arg)
110
+ if requires_quotes?(receive_arg)
111
+ "'#{receive_arg}'"
112
+ else
113
+ receive_arg
114
+ end
115
+ end
116
+
117
+ def normalize_return_arg(return_arg)
118
+ if return_arg.hash_type? && !return_arg.braces?
119
+ "{ #{return_arg.source} }"
120
+ else
121
+ return_arg.source
122
+ end
123
+ end
124
+
125
+ def register_offense(item, repeated_lines, args)
126
+ add_offense(item, message: message(repeated_lines)) do |corrector|
127
+ if item.loc.line > repeated_lines.max
128
+ replace_to_receive_messages(corrector, item, args)
129
+ else
130
+ corrector.remove(item_range_by_whole_lines(item))
131
+ end
132
+ end
133
+ end
134
+
135
+ def message(repeated_lines)
136
+ format(MSG, loc: repeated_lines)
137
+ end
138
+
139
+ def replace_to_receive_messages(corrector, item, args)
140
+ receive_node(item) do |node|
141
+ corrector.replace(node,
142
+ "receive_messages(#{args.join(', ')})")
143
+ end
144
+ end
145
+
146
+ def item_range_by_whole_lines(item)
147
+ range_by_whole_lines(item.source_range, include_final_newline: true)
148
+ end
149
+
150
+ def heredoc_or_splat?(node)
151
+ ((node.str_type? || node.dstr_type?) && node.heredoc?) ||
152
+ node.splat_type?
153
+ end
154
+
155
+ def requires_quotes?(value)
156
+ value.match?(/^:".*?"|=$|^\W+$/)
157
+ end
158
+ end
159
+ end
160
+ end
161
+ end
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module RSpec
6
+ # Checks for redundant predicate matcher.
7
+ #
8
+ # @example
9
+ # # bad
10
+ # expect(foo).to be_exist(bar)
11
+ # expect(foo).not_to be_include(bar)
12
+ # expect(foo).to be_all(bar)
13
+ #
14
+ # # good
15
+ # expect(foo).to exist(bar)
16
+ # expect(foo).not_to include(bar)
17
+ # expect(foo).to all be(bar)
18
+ #
19
+ class RedundantPredicateMatcher < Base
20
+ extend AutoCorrector
21
+
22
+ MSG = 'Use `%<good>s` instead of `%<bad>s`.'
23
+ RESTRICT_ON_SEND =
24
+ %i[be_all be_cover be_end_with be_eql be_equal
25
+ be_exist be_exists be_include be_match
26
+ be_respond_to be_start_with].freeze
27
+
28
+ def on_send(node)
29
+ return if node.parent.block_type? || node.arguments.empty?
30
+ return unless replaceable_arguments?(node)
31
+
32
+ method_name = node.method_name.to_s
33
+ replaced = replaced_method_name(method_name)
34
+ add_offense(node, message: message(method_name,
35
+ replaced)) do |corrector|
36
+ unless node.method?(:be_all)
37
+ corrector.replace(node.loc.selector, replaced)
38
+ end
39
+ end
40
+ end
41
+
42
+ private
43
+
44
+ def message(bad_method, good_method)
45
+ format(MSG, bad: bad_method, good: good_method)
46
+ end
47
+
48
+ def replaceable_arguments?(node)
49
+ if node.method?(:be_all)
50
+ node.first_argument.send_type?
51
+ else
52
+ true
53
+ end
54
+ end
55
+
56
+ def replaced_method_name(method_name)
57
+ name = method_name.to_s.delete_prefix('be_')
58
+ if name == 'exists'
59
+ 'exist'
60
+ else
61
+ name
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module RSpec
6
+ # Checks that `remove_const` is not used in specs.
7
+ #
8
+ # @example
9
+ # # bad
10
+ # it 'does something' do
11
+ # Object.send(:remove_const, :SomeConstant)
12
+ # end
13
+ #
14
+ # before do
15
+ # SomeClass.send(:remove_const, :SomeConstant)
16
+ # end
17
+ #
18
+ class RemoveConst < Base
19
+ include RuboCop::RSpec::Language
20
+
21
+ MSG = 'Do not use remove_const in specs. ' \
22
+ 'Consider using e.g. `stub_const`.'
23
+ RESTRICT_ON_SEND = %i[send __send__].freeze
24
+
25
+ # @!method remove_const(node)
26
+ def_node_matcher :remove_const, <<~PATTERN
27
+ (send _ {:send | :__send__} (sym :remove_const) _)
28
+ PATTERN
29
+
30
+ # Check for offenses
31
+ def on_send(node)
32
+ remove_const(node) do
33
+ add_offense(node)
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
@@ -7,13 +7,13 @@ module RuboCop
7
7
  #
8
8
  # @example
9
9
  #
10
- # it 'is valid' do
11
- # expect(user).to be_valid
12
- # end
10
+ # it 'is valid' do
11
+ # expect(user).to be_valid
12
+ # end
13
13
  #
14
- # it 'validates the user' do
15
- # expect(user).to be_valid
16
- # end
14
+ # it 'validates the user' do
15
+ # expect(user).to be_valid
16
+ # end
17
17
  #
18
18
  class RepeatedExample < Base
19
19
  MSG = "Don't repeat examples within an example group."
@@ -48,7 +48,7 @@ module RuboCop
48
48
  MSG = 'Repeated %<group>s block body on line(s) %<loc>s'
49
49
 
50
50
  # @!method several_example_groups?(node)
51
- def_node_matcher :several_example_groups?, <<-PATTERN
51
+ def_node_matcher :several_example_groups?, <<~PATTERN
52
52
  (begin <#example_group_with_body? #example_group_with_body? ...>)
53
53
  PATTERN
54
54
 
@@ -48,12 +48,12 @@ module RuboCop
48
48
  MSG = 'Repeated %<group>s block description on line(s) %<loc>s'
49
49
 
50
50
  # @!method several_example_groups?(node)
51
- def_node_matcher :several_example_groups?, <<-PATTERN
51
+ def_node_matcher :several_example_groups?, <<~PATTERN
52
52
  (begin <#example_group? #example_group? ...>)
53
53
  PATTERN
54
54
 
55
55
  # @!method doc_string_and_metadata(node)
56
- def_node_matcher :doc_string_and_metadata, <<-PATTERN
56
+ def_node_matcher :doc_string_and_metadata, <<~PATTERN
57
57
  (block (send _ _ $_ $...) ...)
58
58
  PATTERN
59
59
 
@@ -50,7 +50,7 @@ module RuboCop
50
50
  'on line(s) %<repeat>s'
51
51
 
52
52
  # @!method several_include_examples?(node)
53
- def_node_matcher :several_include_examples?, <<-PATTERN
53
+ def_node_matcher :several_include_examples?, <<~PATTERN
54
54
  (begin <#include_examples? #include_examples? ...>)
55
55
  PATTERN
56
56
 
@@ -0,0 +1,125 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module RSpec
6
+ # Checks for repeated calls to subject missing that it is memoized.
7
+ #
8
+ # @example
9
+ # # bad
10
+ # it do
11
+ # subject
12
+ # expect { subject }.to not_change { A.count }
13
+ # end
14
+ #
15
+ # it do
16
+ # expect { subject }.to change { A.count }
17
+ # expect { subject }.to not_change { A.count }
18
+ # end
19
+ #
20
+ # # good
21
+ # it do
22
+ # expect { my_method }.to change { A.count }
23
+ # expect { my_method }.to not_change { A.count }
24
+ # end
25
+ #
26
+ # # also good
27
+ # it do
28
+ # expect { subject.a }.to change { A.count }
29
+ # expect { subject.b }.to not_change { A.count }
30
+ # end
31
+ #
32
+ class RepeatedSubjectCall < Base
33
+ include TopLevelGroup
34
+
35
+ MSG = 'Calls to subject are memoized, this block is misleading'
36
+
37
+ # @!method subject?(node)
38
+ # Find a named or unnamed subject definition
39
+ #
40
+ # @example anonymous subject
41
+ # subject?(parse('subject { foo }').ast) do |name|
42
+ # name # => :subject
43
+ # end
44
+ #
45
+ # @example named subject
46
+ # subject?(parse('subject(:thing) { foo }').ast) do |name|
47
+ # name # => :thing
48
+ # end
49
+ #
50
+ # @param node [RuboCop::AST::Node]
51
+ #
52
+ # @yield [Symbol] subject name
53
+ def_node_matcher :subject?, <<-PATTERN
54
+ (block
55
+ (send nil?
56
+ { #Subjects.all (sym $_) | $#Subjects.all }
57
+ ) args ...)
58
+ PATTERN
59
+
60
+ # @!method subject_calls(node, method_name)
61
+ def_node_search :subject_calls, <<~PATTERN
62
+ (send nil? %)
63
+ PATTERN
64
+
65
+ def on_top_level_group(node)
66
+ @subjects_by_node = detect_subjects_in_scope(node)
67
+
68
+ detect_offenses_in_block(node)
69
+ end
70
+
71
+ private
72
+
73
+ def detect_offense(subject_node)
74
+ return if subject_node.chained?
75
+ return if subject_node.parent.send_type?
76
+ return unless (block_node = expect_block(subject_node))
77
+
78
+ add_offense(block_node)
79
+ end
80
+
81
+ def expect_block(node)
82
+ node.each_ancestor(:block).find { |block| block.method?(:expect) }
83
+ end
84
+
85
+ def detect_offenses_in_block(node, subject_names = [])
86
+ subject_names = [*subject_names, *@subjects_by_node[node]]
87
+
88
+ if example?(node)
89
+ return detect_offenses_in_example(node, subject_names)
90
+ end
91
+
92
+ node.each_child_node(:send, :def, :block, :begin) do |child|
93
+ detect_offenses_in_block(child, subject_names)
94
+ end
95
+ end
96
+
97
+ def detect_offenses_in_example(node, subject_names)
98
+ return unless node.body
99
+
100
+ subjects_used = Hash.new(false)
101
+
102
+ subject_calls(node.body, Set[*subject_names, :subject]).each do |call|
103
+ if subjects_used[call.method_name]
104
+ detect_offense(call)
105
+ else
106
+ subjects_used[call.method_name] = true
107
+ end
108
+ end
109
+ end
110
+
111
+ def detect_subjects_in_scope(node)
112
+ node.each_descendant(:block).with_object({}) do |child, h|
113
+ subject?(child) do |name|
114
+ outer_example_group = child.each_ancestor(:block).find do |a|
115
+ example_group?(a)
116
+ end
117
+
118
+ (h[outer_example_group] ||= []) << name
119
+ end
120
+ end
121
+ end
122
+ end
123
+ end
124
+ end
125
+ end
@@ -48,7 +48,7 @@ module RuboCop
48
48
  def_node_matcher :stub_with_block?, '(block #contains_stub? ...)'
49
49
 
50
50
  # @!method and_return_value(node)
51
- def_node_search :and_return_value, <<-PATTERN
51
+ def_node_search :and_return_value, <<~PATTERN
52
52
  $(send _ :and_return $(...))
53
53
  PATTERN
54
54
 
@@ -76,7 +76,7 @@ module RuboCop
76
76
  return if first_occurrence == occurrence || !first_occurrence.body
77
77
 
78
78
  corrector.insert_after(first_occurrence.body,
79
- "\n#{occurrence.body.source}")
79
+ "\n#{occurrence.body&.source}")
80
80
  corrector.remove(range_by_whole_lines(occurrence.source_range,
81
81
  include_final_newline: true))
82
82
  end
@@ -62,7 +62,7 @@ module RuboCop
62
62
  PATTERN
63
63
 
64
64
  # @!method context?(node)
65
- def_node_search :context?, <<-PATTERN
65
+ def_node_search :context?, <<~PATTERN
66
66
  (send nil?
67
67
  {#Subjects.all #Helpers.all #Includes.context #Hooks.all} ...
68
68
  )
@@ -3,9 +3,13 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module RSpec
6
- # Enforces use of string to titleize shared examples.
6
+ # Checks for consistent style for shared example names.
7
7
  #
8
- # @example
8
+ # Enforces either `string` or `symbol` for shared example names.
9
+ #
10
+ # This cop can be configured using the `EnforcedStyle` option
11
+ #
12
+ # @example `EnforcedStyle: string` (default)
9
13
  # # bad
10
14
  # it_behaves_like :foo_bar_baz
11
15
  # it_should_behave_like :foo_bar_baz
@@ -20,33 +24,66 @@ module RuboCop
20
24
  # shared_examples_for 'foo bar baz'
21
25
  # include_examples 'foo bar baz'
22
26
  #
27
+ # @example `EnforcedStyle: symbol`
28
+ # # bad
29
+ # it_behaves_like 'foo bar baz'
30
+ # it_should_behave_like 'foo bar baz'
31
+ # shared_examples 'foo bar baz'
32
+ # shared_examples_for 'foo bar baz'
33
+ # include_examples 'foo bar baz'
34
+ #
35
+ # # good
36
+ # it_behaves_like :foo_bar_baz
37
+ # it_should_behave_like :foo_bar_baz
38
+ # shared_examples :foo_bar_baz
39
+ # shared_examples_for :foo_bar_baz
40
+ # include_examples :foo_bar_baz
41
+ #
23
42
  class SharedExamples < Base
24
43
  extend AutoCorrector
44
+ include ConfigurableEnforcedStyle
25
45
 
26
46
  # @!method shared_examples(node)
27
47
  def_node_matcher :shared_examples, <<~PATTERN
28
48
  {
29
- (send #rspec? #SharedGroups.all ...)
30
- (send nil? #Includes.all ...)
49
+ (send #rspec? #SharedGroups.all $_ ...)
50
+ (send nil? #Includes.all $_ ...)
31
51
  }
32
52
  PATTERN
33
53
 
34
54
  def on_send(node)
35
- shared_examples(node) do
36
- ast_node = node.first_argument
37
- next unless ast_node&.sym_type?
55
+ shared_examples(node) do |ast_node|
56
+ next unless offense?(ast_node)
38
57
 
39
- checker = Checker.new(ast_node)
40
- add_offense(checker.node, message: checker.message) do |corrector|
41
- corrector.replace(checker.node, checker.preferred_style)
58
+ checker = new_checker(ast_node)
59
+ add_offense(ast_node, message: checker.message) do |corrector|
60
+ corrector.replace(ast_node, checker.preferred_style)
42
61
  end
43
62
  end
44
63
  end
45
64
 
65
+ private
66
+
67
+ def offense?(ast_node)
68
+ if style == :symbol
69
+ ast_node.str_type?
70
+ else # string
71
+ ast_node.sym_type?
72
+ end
73
+ end
74
+
75
+ def new_checker(ast_node)
76
+ if style == :symbol
77
+ SymbolChecker.new(ast_node)
78
+ else # string
79
+ StringChecker.new(ast_node)
80
+ end
81
+ end
82
+
46
83
  # :nodoc:
47
- class Checker
84
+ class SymbolChecker
48
85
  MSG = 'Prefer %<prefer>s over `%<current>s` ' \
49
- 'to titleize shared examples.'
86
+ 'to symbolize shared examples.'
50
87
 
51
88
  attr_reader :node
52
89
 
@@ -55,22 +92,31 @@ module RuboCop
55
92
  end
56
93
 
57
94
  def message
58
- format(MSG, prefer: preferred_style, current: symbol.inspect)
95
+ format(MSG, prefer: preferred_style, current: node.value.inspect)
59
96
  end
60
97
 
61
98
  def preferred_style
62
- string = symbol.to_s.tr('_', ' ')
63
- wrap_with_single_quotes(string)
99
+ ":#{node.value.to_s.downcase.tr(' ', '_')}"
64
100
  end
101
+ end
102
+
103
+ # :nodoc:
104
+ class StringChecker
105
+ MSG = 'Prefer %<prefer>s over `%<current>s` ' \
106
+ 'to titleize shared examples.'
65
107
 
66
- private
108
+ attr_reader :node
67
109
 
68
- def symbol
69
- node.value
110
+ def initialize(node)
111
+ @node = node
70
112
  end
71
113
 
72
- def wrap_with_single_quotes(string)
73
- "'#{string}'"
114
+ def message
115
+ format(MSG, prefer: preferred_style, current: node.value.inspect)
116
+ end
117
+
118
+ def preferred_style
119
+ "'#{node.value.to_s.tr('_', ' ')}'"
74
120
  end
75
121
  end
76
122
  end
@@ -24,7 +24,7 @@ module RuboCop
24
24
  RESTRICT_ON_SEND = %i[receive_message_chain stub_chain].freeze
25
25
 
26
26
  # @!method message_chain(node)
27
- def_node_matcher :message_chain, <<-PATTERN
27
+ def_node_matcher :message_chain, <<~PATTERN
28
28
  (send _ {:receive_message_chain :stub_chain} $_)
29
29
  PATTERN
30
30
 
@@ -81,8 +81,7 @@ module RuboCop
81
81
  end
82
82
 
83
83
  def key_to_arg(node)
84
- key, = *node # rubocop:disable InternalAffairs/NodeDestructuring
85
- node.sym_type? ? ":#{key}" : node.source
84
+ node.sym_type? ? ":#{node.value}" : node.source
86
85
  end
87
86
 
88
87
  def replacement(method)
@@ -23,7 +23,8 @@ module RuboCop
23
23
 
24
24
  MSG = 'Sort metadata alphabetically.'
25
25
 
26
- def on_metadata(symbols, pairs)
26
+ def on_metadata(symbols, hash)
27
+ pairs = hash&.pairs || []
27
28
  return if sorted?(symbols, pairs)
28
29
 
29
30
  crime_scene = crime_scene(symbols, pairs)
@@ -57,7 +58,7 @@ module RuboCop
57
58
 
58
59
  def sort_symbols(symbols)
59
60
  symbols.sort_by do |symbol|
60
- if %i[str sym].include?(symbol.type)
61
+ if symbol.str_type? || symbol.sym_type?
61
62
  symbol.value.to_s.downcase
62
63
  else
63
64
  symbol.source.downcase