rubocop-rspec 1.7.0 → 3.0.2

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 (193) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +955 -79
  3. data/CODE_OF_CONDUCT.md +17 -0
  4. data/MIT-LICENSE.md +1 -2
  5. data/README.md +35 -35
  6. data/config/default.yml +940 -52
  7. data/config/obsoletion.yml +30 -0
  8. data/lib/rubocop/cop/rspec/align_left_let_brace.rb +49 -0
  9. data/lib/rubocop/cop/rspec/align_right_let_brace.rb +49 -0
  10. data/lib/rubocop/cop/rspec/any_instance.rb +10 -13
  11. data/lib/rubocop/cop/rspec/around_block.rb +97 -0
  12. data/lib/rubocop/cop/rspec/base.rb +26 -0
  13. data/lib/rubocop/cop/rspec/be.rb +39 -0
  14. data/lib/rubocop/cop/rspec/be_empty.rb +45 -0
  15. data/lib/rubocop/cop/rspec/be_eq.rb +47 -0
  16. data/lib/rubocop/cop/rspec/be_eql.rb +18 -15
  17. data/lib/rubocop/cop/rspec/be_nil.rb +74 -0
  18. data/lib/rubocop/cop/rspec/before_after_all.rb +45 -0
  19. data/lib/rubocop/cop/rspec/change_by_zero.rb +184 -0
  20. data/lib/rubocop/cop/rspec/class_check.rb +101 -0
  21. data/lib/rubocop/cop/rspec/contain_exactly.rb +56 -0
  22. data/lib/rubocop/cop/rspec/context_method.rb +57 -0
  23. data/lib/rubocop/cop/rspec/context_wording.rb +117 -0
  24. data/lib/rubocop/cop/rspec/describe_class.rb +52 -21
  25. data/lib/rubocop/cop/rspec/describe_method.rb +26 -11
  26. data/lib/rubocop/cop/rspec/describe_symbol.rb +37 -0
  27. data/lib/rubocop/cop/rspec/described_class.rb +181 -34
  28. data/lib/rubocop/cop/rspec/described_class_module_wrapping.rb +38 -0
  29. data/lib/rubocop/cop/rspec/dialect.rb +84 -0
  30. data/lib/rubocop/cop/rspec/duplicated_metadata.rb +58 -0
  31. data/lib/rubocop/cop/rspec/empty_example_group.rb +134 -47
  32. data/lib/rubocop/cop/rspec/empty_hook.rb +49 -0
  33. data/lib/rubocop/cop/rspec/empty_line_after_example.rb +82 -0
  34. data/lib/rubocop/cop/rspec/empty_line_after_example_group.rb +42 -0
  35. data/lib/rubocop/cop/rspec/empty_line_after_final_let.rb +40 -0
  36. data/lib/rubocop/cop/rspec/empty_line_after_hook.rb +82 -0
  37. data/lib/rubocop/cop/rspec/empty_line_after_subject.rb +36 -0
  38. data/lib/rubocop/cop/rspec/empty_metadata.rb +46 -0
  39. data/lib/rubocop/cop/rspec/empty_output.rb +47 -0
  40. data/lib/rubocop/cop/rspec/eq.rb +47 -0
  41. data/lib/rubocop/cop/rspec/example_length.rb +38 -20
  42. data/lib/rubocop/cop/rspec/example_without_description.rb +98 -0
  43. data/lib/rubocop/cop/rspec/example_wording.rb +117 -27
  44. data/lib/rubocop/cop/rspec/excessive_docstring_spacing.rb +110 -0
  45. data/lib/rubocop/cop/rspec/expect_actual.rb +46 -20
  46. data/lib/rubocop/cop/rspec/expect_change.rb +86 -0
  47. data/lib/rubocop/cop/rspec/expect_in_hook.rb +50 -0
  48. data/lib/rubocop/cop/rspec/expect_in_let.rb +42 -0
  49. data/lib/rubocop/cop/rspec/expect_output.rb +50 -0
  50. data/lib/rubocop/cop/rspec/focus.rb +79 -25
  51. data/lib/rubocop/cop/rspec/hook_argument.rb +48 -36
  52. data/lib/rubocop/cop/rspec/hooks_before_examples.rb +81 -0
  53. data/lib/rubocop/cop/rspec/identical_equality_assertion.rb +37 -0
  54. data/lib/rubocop/cop/rspec/implicit_block_expectation.rb +68 -0
  55. data/lib/rubocop/cop/rspec/implicit_expect.rb +100 -0
  56. data/lib/rubocop/cop/rspec/implicit_subject.rb +167 -0
  57. data/lib/rubocop/cop/rspec/indexed_let.rb +112 -0
  58. data/lib/rubocop/cop/rspec/instance_spy.rb +74 -0
  59. data/lib/rubocop/cop/rspec/instance_variable.rb +28 -14
  60. data/lib/rubocop/cop/rspec/is_expected_specify.rb +45 -0
  61. data/lib/rubocop/cop/rspec/it_behaves_like.rb +49 -0
  62. data/lib/rubocop/cop/rspec/iterated_expectation.rb +74 -0
  63. data/lib/rubocop/cop/rspec/leading_subject.rb +57 -29
  64. data/lib/rubocop/cop/rspec/leaky_constant_declaration.rb +127 -0
  65. data/lib/rubocop/cop/rspec/let_before_examples.rb +101 -0
  66. data/lib/rubocop/cop/rspec/let_setup.rb +32 -16
  67. data/lib/rubocop/cop/rspec/match_array.rb +59 -0
  68. data/lib/rubocop/cop/rspec/message_chain.rb +10 -15
  69. data/lib/rubocop/cop/rspec/message_expectation.rb +12 -9
  70. data/lib/rubocop/cop/rspec/message_spies.rb +88 -0
  71. data/lib/rubocop/cop/rspec/metadata_style.rb +202 -0
  72. data/lib/rubocop/cop/rspec/missing_example_group_argument.rb +35 -0
  73. data/lib/rubocop/cop/rspec/missing_expectation_target_method.rb +54 -0
  74. data/lib/rubocop/cop/rspec/mixin/comments_help.rb +38 -0
  75. data/lib/rubocop/cop/rspec/mixin/empty_line_separation.rb +59 -0
  76. data/lib/rubocop/cop/rspec/mixin/file_help.rb +14 -0
  77. data/lib/rubocop/cop/rspec/mixin/final_end_location.rb +19 -0
  78. data/lib/rubocop/cop/rspec/mixin/inside_example_group.rb +29 -0
  79. data/lib/rubocop/cop/rspec/mixin/location_help.rb +37 -0
  80. data/lib/rubocop/cop/rspec/mixin/metadata.rb +63 -0
  81. data/lib/rubocop/cop/rspec/mixin/namespace.rb +23 -0
  82. data/lib/rubocop/cop/rspec/mixin/skip_or_pending.rb +39 -0
  83. data/lib/rubocop/cop/rspec/mixin/top_level_group.rb +54 -0
  84. data/lib/rubocop/cop/rspec/mixin/variable.rb +21 -0
  85. data/lib/rubocop/cop/rspec/multiple_describes.rb +14 -12
  86. data/lib/rubocop/cop/rspec/multiple_expectations.rb +86 -26
  87. data/lib/rubocop/cop/rspec/multiple_memoized_helpers.rb +146 -0
  88. data/lib/rubocop/cop/rspec/multiple_subjects.rb +97 -0
  89. data/lib/rubocop/cop/rspec/named_subject.rb +107 -27
  90. data/lib/rubocop/cop/rspec/nested_groups.rb +84 -47
  91. data/lib/rubocop/cop/rspec/no_expectation_example.rb +102 -0
  92. data/lib/rubocop/cop/rspec/not_to_not.rb +30 -27
  93. data/lib/rubocop/cop/rspec/overwriting_setup.rb +74 -0
  94. data/lib/rubocop/cop/rspec/pending.rb +80 -0
  95. data/lib/rubocop/cop/rspec/pending_without_reason.rb +159 -0
  96. data/lib/rubocop/cop/rspec/predicate_matcher.rb +341 -0
  97. data/lib/rubocop/cop/rspec/receive_counts.rb +89 -0
  98. data/lib/rubocop/cop/rspec/receive_messages.rb +161 -0
  99. data/lib/rubocop/cop/rspec/receive_never.rb +41 -0
  100. data/lib/rubocop/cop/rspec/redundant_around.rb +65 -0
  101. data/lib/rubocop/cop/rspec/redundant_predicate_matcher.rb +67 -0
  102. data/lib/rubocop/cop/rspec/remove_const.rb +39 -0
  103. data/lib/rubocop/cop/rspec/repeated_description.rb +98 -0
  104. data/lib/rubocop/cop/rspec/repeated_example.rb +53 -0
  105. data/lib/rubocop/cop/rspec/repeated_example_group_body.rb +100 -0
  106. data/lib/rubocop/cop/rspec/repeated_example_group_description.rb +96 -0
  107. data/lib/rubocop/cop/rspec/repeated_include_example.rb +105 -0
  108. data/lib/rubocop/cop/rspec/repeated_subject_call.rb +125 -0
  109. data/lib/rubocop/cop/rspec/return_from_stub.rb +169 -0
  110. data/lib/rubocop/cop/rspec/scattered_let.rb +59 -0
  111. data/lib/rubocop/cop/rspec/scattered_setup.rb +92 -0
  112. data/lib/rubocop/cop/rspec/shared_context.rb +107 -0
  113. data/lib/rubocop/cop/rspec/shared_examples.rb +125 -0
  114. data/lib/rubocop/cop/rspec/single_argument_message_chain.rb +93 -0
  115. data/lib/rubocop/cop/rspec/skip_block_inside_example.rb +46 -0
  116. data/lib/rubocop/cop/rspec/sort_metadata.rb +71 -0
  117. data/lib/rubocop/cop/rspec/spec_file_path_format.rb +133 -0
  118. data/lib/rubocop/cop/rspec/spec_file_path_suffix.rb +40 -0
  119. data/lib/rubocop/cop/rspec/stubbed_mock.rb +176 -0
  120. data/lib/rubocop/cop/rspec/subject_declaration.rb +46 -0
  121. data/lib/rubocop/cop/rspec/subject_stub.rb +93 -74
  122. data/lib/rubocop/cop/rspec/undescriptive_literals_description.rb +69 -0
  123. data/lib/rubocop/cop/rspec/unspecified_exception.rb +67 -0
  124. data/lib/rubocop/cop/rspec/variable_definition.rb +77 -0
  125. data/lib/rubocop/cop/rspec/variable_name.rb +68 -0
  126. data/lib/rubocop/cop/rspec/verified_double_reference.rb +111 -0
  127. data/lib/rubocop/cop/rspec/verified_doubles.rb +28 -14
  128. data/lib/rubocop/cop/rspec/void_expect.rb +60 -0
  129. data/lib/rubocop/cop/rspec/yield.rb +82 -0
  130. data/lib/rubocop/cop/rspec_cops.rb +112 -0
  131. data/lib/rubocop/rspec/align_let_brace.rb +63 -0
  132. data/lib/rubocop/rspec/concept.rb +33 -0
  133. data/lib/rubocop/rspec/config_formatter.rb +27 -4
  134. data/lib/rubocop/rspec/cop/generator.rb +25 -0
  135. data/lib/rubocop/rspec/corrector/move_node.rb +51 -0
  136. data/lib/rubocop/rspec/description_extractor.rb +60 -18
  137. data/lib/rubocop/rspec/example.rb +37 -0
  138. data/lib/rubocop/rspec/example_group.rb +67 -0
  139. data/lib/rubocop/rspec/hook.rb +79 -0
  140. data/lib/rubocop/rspec/inject.rb +3 -1
  141. data/lib/rubocop/rspec/language.rb +184 -41
  142. data/lib/rubocop/rspec/node.rb +19 -0
  143. data/lib/rubocop/rspec/shared_contexts/default_rspec_language_config_context.rb +29 -0
  144. data/lib/rubocop/rspec/version.rb +1 -1
  145. data/lib/rubocop/rspec/wording.rb +61 -19
  146. data/lib/rubocop/rspec.rb +6 -2
  147. data/lib/rubocop-rspec.rb +45 -34
  148. metadata +130 -195
  149. data/Gemfile +0 -13
  150. data/Rakefile +0 -48
  151. data/lib/rubocop/cop/rspec/file_path.rb +0 -83
  152. data/lib/rubocop/rspec/language/node_pattern.rb +0 -16
  153. data/lib/rubocop/rspec/spec_only.rb +0 -61
  154. data/lib/rubocop/rspec/top_level_describe.rb +0 -61
  155. data/lib/rubocop/rspec/util.rb +0 -19
  156. data/rubocop-rspec.gemspec +0 -42
  157. data/spec/expect_violation/expectation_spec.rb +0 -85
  158. data/spec/project/changelog_spec.rb +0 -81
  159. data/spec/project/default_config_spec.rb +0 -52
  160. data/spec/project/project_requires_spec.rb +0 -8
  161. data/spec/rubocop/cop/rspec/any_instance_spec.rb +0 -30
  162. data/spec/rubocop/cop/rspec/be_eql_spec.rb +0 -59
  163. data/spec/rubocop/cop/rspec/describe_class_spec.rb +0 -113
  164. data/spec/rubocop/cop/rspec/describe_method_spec.rb +0 -32
  165. data/spec/rubocop/cop/rspec/described_class_spec.rb +0 -219
  166. data/spec/rubocop/cop/rspec/empty_example_group_spec.rb +0 -79
  167. data/spec/rubocop/cop/rspec/example_length_spec.rb +0 -117
  168. data/spec/rubocop/cop/rspec/example_wording_spec.rb +0 -82
  169. data/spec/rubocop/cop/rspec/expect_actual_spec.rb +0 -136
  170. data/spec/rubocop/cop/rspec/file_path_spec.rb +0 -236
  171. data/spec/rubocop/cop/rspec/focus_spec.rb +0 -130
  172. data/spec/rubocop/cop/rspec/hook_argument_spec.rb +0 -189
  173. data/spec/rubocop/cop/rspec/instance_variable_spec.rb +0 -75
  174. data/spec/rubocop/cop/rspec/leading_subject_spec.rb +0 -54
  175. data/spec/rubocop/cop/rspec/let_setup_spec.rb +0 -66
  176. data/spec/rubocop/cop/rspec/message_chain_spec.rb +0 -21
  177. data/spec/rubocop/cop/rspec/message_expectation_spec.rb +0 -63
  178. data/spec/rubocop/cop/rspec/multiple_describes_spec.rb +0 -28
  179. data/spec/rubocop/cop/rspec/multiple_expectations_spec.rb +0 -84
  180. data/spec/rubocop/cop/rspec/named_subject_spec.rb +0 -62
  181. data/spec/rubocop/cop/rspec/nested_groups_spec.rb +0 -55
  182. data/spec/rubocop/cop/rspec/not_to_not_spec.rb +0 -57
  183. data/spec/rubocop/cop/rspec/subject_stub_spec.rb +0 -183
  184. data/spec/rubocop/cop/rspec/verified_doubles_spec.rb +0 -71
  185. data/spec/rubocop/rspec/config_formatter_spec.rb +0 -48
  186. data/spec/rubocop/rspec/description_extractor_spec.rb +0 -35
  187. data/spec/rubocop/rspec/language/selector_set_spec.rb +0 -29
  188. data/spec/rubocop/rspec/spec_only_spec.rb +0 -97
  189. data/spec/rubocop/rspec/util/one_spec.rb +0 -21
  190. data/spec/rubocop/rspec/wording_spec.rb +0 -44
  191. data/spec/shared/rspec_only_cop_behavior.rb +0 -68
  192. data/spec/spec_helper.rb +0 -41
  193. data/spec/support/expect_violation.rb +0 -166
@@ -3,58 +3,135 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module RSpec
6
- # Checks that example descriptions do not start with "should".
6
+ # Checks for common mistakes in example descriptions.
7
+ #
8
+ # This cop will correct docstrings that begin with 'should' and 'it'.
9
+ # This cop will also look for insufficient examples and call them out.
7
10
  #
8
11
  # @see http://betterspecs.org/#should
9
12
  #
10
13
  # The autocorrect is experimental - use with care! It can be configured
11
14
  # with CustomTransform (e.g. have => has) and IgnoredWords (e.g. only).
12
15
  #
16
+ # Use the DisallowedExamples setting to prevent unclear or insufficient
17
+ # descriptions. Please note that this config will not be treated as
18
+ # case sensitive.
19
+ #
13
20
  # @example
14
21
  # # bad
15
22
  # it 'should find nothing' do
16
23
  # end
17
24
  #
25
+ # it 'will find nothing' do
26
+ # end
27
+ #
18
28
  # # good
19
29
  # it 'finds nothing' do
20
30
  # end
21
- class ExampleWording < Cop
22
- include RuboCop::RSpec::SpecOnly
31
+ #
32
+ # @example
33
+ # # bad
34
+ # it 'it does things' do
35
+ # end
36
+ #
37
+ # # good
38
+ # it 'does things' do
39
+ # end
40
+ #
41
+ # @example `DisallowedExamples: ['works']` (default)
42
+ # # bad
43
+ # it 'works' do
44
+ # end
45
+ #
46
+ # # good
47
+ # it 'marks the task as done' do
48
+ # end
49
+ class ExampleWording < Base
50
+ extend AutoCorrector
51
+
52
+ MSG_SHOULD = 'Do not use should when describing your tests.'
53
+ MSG_WILL = 'Do not use the future tense when describing your tests.'
54
+ MSG_IT = "Do not repeat 'it' when describing your tests."
55
+ MSG_INSUFFICIENT_DESCRIPTION = 'Your example description is ' \
56
+ 'insufficient.'
23
57
 
24
- MSG = 'Do not use should when describing your tests.'.freeze
58
+ SHOULD_PREFIX = /\Ashould(?:n't)?\b/i.freeze
59
+ WILL_PREFIX = /\A(?:will|won't)\b/i.freeze
60
+ IT_PREFIX = /\Ait /i.freeze
25
61
 
26
- def on_block(node) # rubocop:disable Metrics/AbcSize,Metrics/MethodLength,Metrics/LineLength
27
- method, = *node
28
- _, method_name, *args = *method
62
+ # @!method it_description(node)
63
+ def_node_matcher :it_description, <<~PATTERN
64
+ (block (send _ :it ${
65
+ (str $_)
66
+ (dstr (str $_ ) ...)
67
+ } ...) ...)
68
+ PATTERN
29
69
 
30
- return unless method_name.equal?(:it)
70
+ # rubocop:disable Metrics/MethodLength
71
+ def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler
72
+ it_description(node) do |description_node, message|
73
+ if message.match?(SHOULD_PREFIX)
74
+ add_wording_offense(description_node, MSG_SHOULD)
75
+ elsif message.match?(WILL_PREFIX)
76
+ add_wording_offense(description_node, MSG_WILL)
77
+ elsif message.match?(IT_PREFIX)
78
+ add_wording_offense(description_node, MSG_IT)
79
+ elsif insufficient_docstring?(description_node)
80
+ add_offense(docstring(description_node),
81
+ message: MSG_INSUFFICIENT_DESCRIPTION)
82
+ end
83
+ end
84
+ end
85
+ # rubocop:enable Metrics/MethodLength
31
86
 
32
- arguments = args.first.to_a
33
- message = arguments.first.to_s
34
- return unless message.downcase.start_with?('should')
87
+ private
35
88
 
36
- arg1 = args.first.loc.expression
37
- message = Parser::Source::Range.new(arg1.source_buffer,
38
- arg1.begin_pos + 1,
39
- arg1.end_pos - 1)
89
+ def add_wording_offense(node, message)
90
+ docstring = docstring(node)
40
91
 
41
- add_offense(message, message)
92
+ add_offense(docstring, message: message) do |corrector|
93
+ next if node.heredoc?
94
+
95
+ corrector.replace(docstring, replacement_text(node))
96
+ end
42
97
  end
43
98
 
44
- def autocorrect(range)
45
- lambda do |corrector|
46
- corrector.replace(
47
- range,
48
- RuboCop::RSpec::Wording.new(
49
- range.source,
50
- ignore: ignored_words,
51
- replace: custom_transform
52
- ).rewrite
53
- )
99
+ def docstring(node)
100
+ expr = node.source_range
101
+
102
+ Parser::Source::Range.new(
103
+ expr.source_buffer,
104
+ expr.begin_pos + 1,
105
+ expr.end_pos - 1
106
+ )
107
+ end
108
+
109
+ def replacement_text(node)
110
+ text = text(node)
111
+
112
+ if text.match?(SHOULD_PREFIX) || text.match?(WILL_PREFIX)
113
+ RuboCop::RSpec::Wording.new(
114
+ text,
115
+ ignore: ignored_words,
116
+ replace: custom_transform
117
+ ).rewrite
118
+ else
119
+ text.sub(IT_PREFIX, '')
54
120
  end
55
121
  end
56
122
 
57
- private
123
+ # Recursive processing is required to process nested dstr nodes
124
+ # that is the case for \-separated multiline strings with interpolation.
125
+ def text(node)
126
+ case node.type
127
+ when :dstr
128
+ node.node_parts.map { |child_node| text(child_node) }.join
129
+ when :str
130
+ node.value
131
+ when :begin
132
+ node.source
133
+ end
134
+ end
58
135
 
59
136
  def custom_transform
60
137
  cop_config.fetch('CustomTransform', {})
@@ -63,6 +140,19 @@ module RuboCop
63
140
  def ignored_words
64
141
  cop_config.fetch('IgnoredWords', [])
65
142
  end
143
+
144
+ def insufficient_docstring?(description_node)
145
+ insufficient_examples.include?(preprocess(text(description_node)))
146
+ end
147
+
148
+ def insufficient_examples
149
+ examples = cop_config.fetch('DisallowedExamples', [])
150
+ examples.map! { |example| preprocess(example) }
151
+ end
152
+
153
+ def preprocess(message)
154
+ message.strip.squeeze(' ').downcase
155
+ end
66
156
  end
67
157
  end
68
158
  end
@@ -0,0 +1,110 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module RSpec
6
+ # Checks for excessive whitespace in example descriptions.
7
+ #
8
+ # @example
9
+ # # bad
10
+ # it ' has excessive spacing ' do
11
+ # end
12
+ #
13
+ # # good
14
+ # it 'has excessive spacing' do
15
+ # end
16
+ #
17
+ # @example
18
+ # # bad
19
+ # context ' when a condition is met ' do
20
+ # end
21
+ #
22
+ # # good
23
+ # context 'when a condition is met' do
24
+ # end
25
+ #
26
+ class ExcessiveDocstringSpacing < Base
27
+ extend AutoCorrector
28
+
29
+ MSG = 'Excessive whitespace.'
30
+
31
+ # @!method example_description(node)
32
+ def_node_matcher :example_description, <<~PATTERN
33
+ (send _ {#Examples.all #ExampleGroups.all} ${
34
+ $str
35
+ $(dstr ({str dstr `sym} ...) ...)
36
+ } ...)
37
+ PATTERN
38
+
39
+ def on_send(node)
40
+ example_description(node) do |description_node, message|
41
+ return if description_node.heredoc?
42
+
43
+ text = text(message)
44
+
45
+ return unless excessive_whitespace?(text)
46
+
47
+ add_whitespace_offense(description_node, text)
48
+ end
49
+ end
50
+
51
+ private
52
+
53
+ # @param text [String]
54
+ def excessive_whitespace?(text)
55
+ text.match?(/
56
+ # Leading space
57
+ \A[[:blank:]]
58
+ |
59
+ # Trailing space
60
+ [[:blank:]]\z
61
+ |
62
+ # Two or more consecutive spaces, except if they are leading spaces
63
+ [^[[:space:]]][[:blank:]]{2,}[^[[:blank:]]]
64
+ /x)
65
+ end
66
+
67
+ # @param text [String]
68
+ def strip_excessive_whitespace(text)
69
+ text
70
+ .gsub(/[[:blank:]]{2,}/, ' ')
71
+ .gsub(/\A[[:blank:]]|[[:blank:]]\z/, '')
72
+ end
73
+
74
+ # @param node [RuboCop::AST::Node]
75
+ # @param text [String]
76
+ def add_whitespace_offense(node, text)
77
+ docstring = docstring(node)
78
+ corrected = strip_excessive_whitespace(text)
79
+
80
+ add_offense(docstring) do |corrector|
81
+ corrector.replace(docstring, corrected)
82
+ end
83
+ end
84
+
85
+ def docstring(node)
86
+ expr = node.source_range
87
+
88
+ Parser::Source::Range.new(
89
+ expr.source_buffer,
90
+ expr.begin_pos + 1,
91
+ expr.end_pos - 1
92
+ )
93
+ end
94
+
95
+ # Recursive processing is required to process nested dstr nodes
96
+ # that is the case for \-separated multiline strings with interpolation.
97
+ def text(node)
98
+ case node.type
99
+ when :dstr
100
+ node.node_parts.map { |child_node| text(child_node) }.join
101
+ when :str, :sym
102
+ node.value
103
+ when :begin
104
+ node.source
105
+ end
106
+ end
107
+ end
108
+ end
109
+ end
110
+ end
@@ -5,6 +5,8 @@ module RuboCop
5
5
  module RSpec
6
6
  # Checks for `expect(...)` calls containing literal values.
7
7
  #
8
+ # Autocorrection is performed when the expected is not a literal.
9
+ #
8
10
  # @example
9
11
  # # bad
10
12
  # expect(5).to eq(price)
@@ -16,12 +18,17 @@ module RuboCop
16
18
  # expect(pattern).to eq(/foo/)
17
19
  # expect(name).to eq("John")
18
20
  #
19
- class ExpectActual < Cop
20
- include RuboCop::RSpec::SpecOnly
21
+ # # bad (not supported autocorrection)
22
+ # expect(false).to eq(true)
23
+ #
24
+ class ExpectActual < Base
25
+ extend AutoCorrector
26
+
27
+ MSG = 'Provide the actual value you are testing to `expect(...)`.'
21
28
 
22
- MSG = 'Provide the actual you are testing to `expect(...)`'.freeze
29
+ RESTRICT_ON_SEND = Runners.all
23
30
 
24
- SIMPLE_LITERALS = %i(
31
+ SIMPLE_LITERALS = %i[
25
32
  true
26
33
  false
27
34
  nil
@@ -32,37 +39,56 @@ module RuboCop
32
39
  complex
33
40
  rational
34
41
  regopt
35
- ).freeze
42
+ ].freeze
36
43
 
37
- COMPLEX_LITERALS = %i(
44
+ COMPLEX_LITERALS = %i[
38
45
  array
39
46
  hash
40
47
  pair
41
48
  irange
42
49
  erange
43
50
  regexp
44
- ).freeze
51
+ ].freeze
52
+
53
+ SKIPPED_MATCHERS = %i[route_to be_routable].freeze
54
+ CORRECTABLE_MATCHERS = %i[eq eql equal be].freeze
55
+
56
+ # @!method expect_literal(node)
57
+ def_node_matcher :expect_literal, <<~PATTERN
58
+ (send
59
+ (send nil? :expect $#literal?)
60
+ #Runners.all
61
+ ${
62
+ (send (send nil? $:be) :== $_)
63
+ (send nil? $_ $_ ...)
64
+ }
65
+ )
66
+ PATTERN
45
67
 
46
- def_node_matcher :expect, '(send _ :expect $_)'
68
+ def on_send(node) # rubocop:disable Metrics/MethodLength
69
+ expect_literal(node) do |actual, send_node, matcher, expected|
70
+ next if SKIPPED_MATCHERS.include?(matcher)
47
71
 
48
- def on_send(node)
49
- expect_literal(node) do |argument|
50
- add_offense(argument, :expression)
72
+ add_offense(actual.source_range) do |corrector|
73
+ next unless CORRECTABLE_MATCHERS.include?(matcher)
74
+ next if literal?(expected)
75
+
76
+ corrector.replace(actual, expected.source)
77
+ if matcher == :be
78
+ corrector.replace(expected, actual.source)
79
+ else
80
+ corrector.replace(send_node, "#{matcher}(#{actual.source})")
81
+ end
82
+ end
51
83
  end
52
84
  end
53
85
 
54
86
  private
55
87
 
56
- # This is not implement using a NodePattern because it seems
88
+ # This is not implemented using a NodePattern because it seems
57
89
  # 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
90
  def literal?(node)
65
- simple_literal?(node) || complex_literal?(node)
91
+ node && (simple_literal?(node) || complex_literal?(node))
66
92
  end
67
93
 
68
94
  def simple_literal?(node)
@@ -71,7 +97,7 @@ module RuboCop
71
97
 
72
98
  def complex_literal?(node)
73
99
  COMPLEX_LITERALS.include?(node.type) &&
74
- node.each_child_node.all?(&method(:literal?))
100
+ node.each_child_node.all? { |child_node| literal?(child_node) }
75
101
  end
76
102
  end
77
103
  end
@@ -0,0 +1,86 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module RSpec
6
+ # Checks for consistent style of change matcher.
7
+ #
8
+ # Enforces either passing object and attribute as arguments to the matcher
9
+ # or passing a block that reads the attribute value.
10
+ #
11
+ # This cop can be configured using the `EnforcedStyle` option.
12
+ #
13
+ # @example `EnforcedStyle: method_call` (default)
14
+ # # bad
15
+ # expect { run }.to change { Foo.bar }
16
+ # expect { run }.to change { foo.baz }
17
+ #
18
+ # # good
19
+ # expect { run }.to change(Foo, :bar)
20
+ # expect { run }.to change(foo, :baz)
21
+ # # also good when there are arguments or chained method calls
22
+ # expect { run }.to change { Foo.bar(:count) }
23
+ # expect { run }.to change { user.reload.name }
24
+ #
25
+ # @example `EnforcedStyle: block`
26
+ # # bad
27
+ # expect { run }.to change(Foo, :bar)
28
+ #
29
+ # # good
30
+ # expect { run }.to change { Foo.bar }
31
+ #
32
+ class ExpectChange < Base
33
+ extend AutoCorrector
34
+ include ConfigurableEnforcedStyle
35
+
36
+ MSG_BLOCK = 'Prefer `change(%<obj>s, :%<attr>s)`.'
37
+ MSG_CALL = 'Prefer `change { %<obj>s.%<attr>s }`.'
38
+ RESTRICT_ON_SEND = %i[change].freeze
39
+
40
+ # @!method expect_change_with_arguments(node)
41
+ def_node_matcher :expect_change_with_arguments, <<~PATTERN
42
+ (send nil? :change $_ ({sym str} $_))
43
+ PATTERN
44
+
45
+ # @!method expect_change_with_block(node)
46
+ def_node_matcher :expect_change_with_block, <<~PATTERN
47
+ (block
48
+ (send nil? :change)
49
+ (args)
50
+ (send
51
+ ${
52
+ (send nil? _) # change { user.name }
53
+ const # change { User.count }
54
+ }
55
+ $_
56
+ )
57
+ )
58
+ PATTERN
59
+
60
+ def on_send(node)
61
+ return unless style == :block
62
+
63
+ expect_change_with_arguments(node) do |receiver, message|
64
+ msg = format(MSG_CALL, obj: receiver.source, attr: message)
65
+ add_offense(node, message: msg) do |corrector|
66
+ replacement = "change { #{receiver.source}.#{message} }"
67
+ corrector.replace(node, replacement)
68
+ end
69
+ end
70
+ end
71
+
72
+ def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler
73
+ return unless style == :method_call
74
+
75
+ expect_change_with_block(node) do |receiver, message|
76
+ msg = format(MSG_BLOCK, obj: receiver.source, attr: message)
77
+ add_offense(node, message: msg) do |corrector|
78
+ replacement = "change(#{receiver.source}, :#{message})"
79
+ corrector.replace(node, replacement)
80
+ end
81
+ end
82
+ end
83
+ end
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module RSpec
6
+ # Do not use `expect` in hooks such as `before`.
7
+ #
8
+ # @example
9
+ # # bad
10
+ # before do
11
+ # expect(something).to eq 'foo'
12
+ # end
13
+ #
14
+ # # bad
15
+ # after do
16
+ # expect_any_instance_of(Something).to receive(:foo)
17
+ # end
18
+ #
19
+ # # good
20
+ # it do
21
+ # expect(something).to eq 'foo'
22
+ # end
23
+ #
24
+ class ExpectInHook < Base
25
+ MSG = 'Do not use `%<expect>s` in `%<hook>s` hook'
26
+
27
+ # @!method expectation(node)
28
+ def_node_search :expectation, '(send nil? #Expectations.all ...)'
29
+
30
+ def on_block(node)
31
+ return unless hook?(node)
32
+ return if node.body.nil?
33
+
34
+ expectation(node.body) do |expect|
35
+ add_offense(expect.loc.selector,
36
+ message: message(expect, node))
37
+ end
38
+ end
39
+
40
+ alias on_numblock on_block
41
+
42
+ private
43
+
44
+ def message(expect, hook)
45
+ format(MSG, expect: expect.method_name, hook: hook.method_name)
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module RSpec
6
+ # Do not use `expect` in let.
7
+ #
8
+ # @example
9
+ # # bad
10
+ # let(:foo) do
11
+ # expect(something).to eq 'foo'
12
+ # end
13
+ #
14
+ # # good
15
+ # it do
16
+ # expect(something).to eq 'foo'
17
+ # end
18
+ #
19
+ class ExpectInLet < Base
20
+ MSG = 'Do not use `%<expect>s` in let'
21
+
22
+ # @!method expectation(node)
23
+ def_node_search :expectation, '(send nil? #Expectations.all ...)'
24
+
25
+ def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler
26
+ return unless let?(node)
27
+ return if node.body.nil?
28
+
29
+ expectation(node.body) do |expect|
30
+ add_offense(expect.loc.selector, message: message(expect))
31
+ end
32
+ end
33
+
34
+ private
35
+
36
+ def message(expect)
37
+ format(MSG, expect: expect.method_name)
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module RSpec
6
+ # Checks for opportunities to use `expect { ... }.to output`.
7
+ #
8
+ # @example
9
+ # # bad
10
+ # $stdout = StringIO.new
11
+ # my_app.print_report
12
+ # $stdout = STDOUT
13
+ # expect($stdout.string).to eq('Hello World')
14
+ #
15
+ # # good
16
+ # expect { my_app.print_report }.to output('Hello World').to_stdout
17
+ #
18
+ class ExpectOutput < Base
19
+ MSG = 'Use `expect { ... }.to output(...).to_%<name>s` ' \
20
+ 'instead of mutating $%<name>s.'
21
+
22
+ def on_gvasgn(node)
23
+ return unless inside_example_scope?(node)
24
+
25
+ name = node.name[1..]
26
+ return unless name.eql?('stdout') || name.eql?('stderr')
27
+
28
+ add_offense(node.loc.name, message: format(MSG, name: name))
29
+ end
30
+
31
+ private
32
+
33
+ # Detect if we are inside the scope of a single example
34
+ #
35
+ # We want to encourage using `expect { ... }.to output` so
36
+ # we only care about situations where you would replace with
37
+ # an expectation. Therefore, assignments to stderr or stdout
38
+ # within a `before(:all)` or otherwise outside of an example
39
+ # don't matter.
40
+ def inside_example_scope?(node)
41
+ return false if node.nil? || example_group?(node)
42
+ return true if example?(node)
43
+ return RuboCop::RSpec::Hook.new(node).example? if hook?(node)
44
+
45
+ inside_example_scope?(node.parent)
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end