rubocop-rspec 2.18.1 → 2.20.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 (67) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +37 -2
  3. data/README.md +1 -1
  4. data/config/default.yml +46 -1
  5. data/lib/rubocop/cop/rspec/be_empty.rb +44 -0
  6. data/lib/rubocop/cop/rspec/be_nil.rb +2 -2
  7. data/lib/rubocop/cop/rspec/change_by_zero.rb +3 -3
  8. data/lib/rubocop/cop/rspec/contain_exactly.rb +56 -0
  9. data/lib/rubocop/cop/rspec/context_wording.rb +13 -5
  10. data/lib/rubocop/cop/rspec/describe_method.rb +16 -8
  11. data/lib/rubocop/cop/rspec/described_class.rb +2 -1
  12. data/lib/rubocop/cop/rspec/described_class_module_wrapping.rb +7 -5
  13. data/lib/rubocop/cop/rspec/dialect.rb +1 -1
  14. data/lib/rubocop/cop/rspec/duplicated_metadata.rb +1 -1
  15. data/lib/rubocop/cop/rspec/empty_example_group.rb +7 -7
  16. data/lib/rubocop/cop/rspec/empty_hook.rb +2 -2
  17. data/lib/rubocop/cop/rspec/empty_line_after_example_group.rb +1 -1
  18. data/lib/rubocop/cop/rspec/example_wording.rb +1 -1
  19. data/lib/rubocop/cop/rspec/excessive_docstring_spacing.rb +1 -1
  20. data/lib/rubocop/cop/rspec/expect_actual.rb +2 -2
  21. data/lib/rubocop/cop/rspec/expect_in_hook.rb +1 -1
  22. data/lib/rubocop/cop/rspec/factory_bot/attribute_defined_statically.rb +1 -1
  23. data/lib/rubocop/cop/rspec/factory_bot/consistent_parentheses_style.rb +3 -3
  24. data/lib/rubocop/cop/rspec/factory_bot/syntax_methods.rb +2 -2
  25. data/lib/rubocop/cop/rspec/file_path.rb +1 -1
  26. data/lib/rubocop/cop/rspec/focus.rb +4 -5
  27. data/lib/rubocop/cop/rspec/hook_argument.rb +12 -9
  28. data/lib/rubocop/cop/rspec/hooks_before_examples.rb +5 -3
  29. data/lib/rubocop/cop/rspec/indexed_let.rb +76 -0
  30. data/lib/rubocop/cop/rspec/let_before_examples.rb +4 -4
  31. data/lib/rubocop/cop/rspec/let_setup.rb +6 -8
  32. data/lib/rubocop/cop/rspec/match_array.rb +59 -0
  33. data/lib/rubocop/cop/rspec/mixin/empty_line_separation.rb +1 -2
  34. data/lib/rubocop/cop/rspec/mixin/location_help.rb +37 -0
  35. data/lib/rubocop/cop/rspec/mixin/skip_or_pending.rb +20 -4
  36. data/lib/rubocop/cop/rspec/multiple_expectations.rb +2 -1
  37. data/lib/rubocop/cop/rspec/named_subject.rb +6 -4
  38. data/lib/rubocop/cop/rspec/no_expectation_example.rb +2 -5
  39. data/lib/rubocop/cop/rspec/overwriting_setup.rb +3 -1
  40. data/lib/rubocop/cop/rspec/pending.rb +12 -12
  41. data/lib/rubocop/cop/rspec/pending_without_reason.rb +74 -36
  42. data/lib/rubocop/cop/rspec/predicate_matcher.rb +9 -35
  43. data/lib/rubocop/cop/rspec/rails/have_http_status.rb +8 -5
  44. data/lib/rubocop/cop/rspec/rails/http_status.rb +89 -33
  45. data/lib/rubocop/cop/rspec/rails/inferred_spec_type.rb +4 -4
  46. data/lib/rubocop/cop/rspec/rails/minitest_assertions.rb +5 -5
  47. data/lib/rubocop/cop/rspec/rails/travel_around.rb +92 -0
  48. data/lib/rubocop/cop/rspec/receive_counts.rb +1 -1
  49. data/lib/rubocop/cop/rspec/redundant_around.rb +65 -0
  50. data/lib/rubocop/cop/rspec/repeated_example_group_body.rb +3 -6
  51. data/lib/rubocop/cop/rspec/repeated_example_group_description.rb +3 -6
  52. data/lib/rubocop/cop/rspec/repeated_include_example.rb +3 -4
  53. data/lib/rubocop/cop/rspec/scattered_setup.rb +23 -6
  54. data/lib/rubocop/cop/rspec/shared_context.rb +12 -13
  55. data/lib/rubocop/cop/rspec/shared_examples.rb +6 -4
  56. data/lib/rubocop/cop/rspec/skip_block_inside_example.rb +46 -0
  57. data/lib/rubocop/cop/rspec/sort_metadata.rb +2 -2
  58. data/lib/rubocop/cop/rspec/variable_definition.rb +3 -0
  59. data/lib/rubocop/cop/rspec/variable_name.rb +4 -1
  60. data/lib/rubocop/cop/rspec/verified_double_reference.rb +3 -3
  61. data/lib/rubocop/cop/rspec_cops.rb +7 -0
  62. data/lib/rubocop/rspec/example_group.rb +6 -8
  63. data/lib/rubocop/rspec/language/node_pattern.rb +26 -0
  64. data/lib/rubocop/rspec/language.rb +25 -16
  65. data/lib/rubocop/rspec/version.rb +1 -1
  66. data/lib/rubocop-rspec.rb +1 -0
  67. metadata +11 -3
@@ -9,6 +9,7 @@ module RuboCop
9
9
  # @example
10
10
  # # bad
11
11
  # expect(response.status).to be(200)
12
+ # expect(response.code).to eq("200")
12
13
  #
13
14
  # # good
14
15
  # expect(response).to have_http_status(200)
@@ -18,7 +19,7 @@ module RuboCop
18
19
 
19
20
  MSG =
20
21
  'Prefer `expect(response).%<to>s have_http_status(%<status>i)` ' \
21
- 'over `expect(response.status).%<to>s %<match>s`.'
22
+ 'over `%<bad_code>s`.'
22
23
 
23
24
  RUNNERS = %i[to to_not not_to].to_set
24
25
  RESTRICT_ON_SEND = RUNNERS
@@ -27,19 +28,21 @@ module RuboCop
27
28
  def_node_matcher :match_status, <<-PATTERN
28
29
  (send
29
30
  (send nil? :expect
30
- $(send (send nil? :response) :status)
31
+ $(send (send nil? :response) {:status :code})
31
32
  )
32
33
  $RUNNERS
33
- $(send nil? {:be :eq :eql :equal} (int $_))
34
+ $(send nil? {:be :eq :eql :equal} ({int str} $_))
34
35
  )
35
36
  PATTERN
36
37
 
37
38
  def on_send(node)
38
39
  match_status(node) do |response_status, to, match, status|
39
- message = format(MSG, to: to, match: match.source, status: status)
40
+ message = format(MSG, to: to, status: status,
41
+ bad_code: node.source)
40
42
  add_offense(node, message: message) do |corrector|
41
- corrector.replace(response_status.source_range, 'response')
43
+ corrector.replace(response_status, 'response')
42
44
  corrector.replace(match.loc.selector, 'have_http_status')
45
+ corrector.replace(match.first_argument, status.to_i.to_s)
43
46
  end
44
47
  end
45
48
  end
@@ -8,6 +8,11 @@ module RuboCop
8
8
  module Rails
9
9
  # Enforces use of symbolic or numeric value to describe HTTP status.
10
10
  #
11
+ # This cop inspects only `have_http_status` calls.
12
+ # So, this cop does not check if a method starting with `be_*` is used
13
+ # when setting for `EnforcedStyle: symbolic` or
14
+ # `EnforcedStyle: numeric`.
15
+ #
11
16
  # @example `EnforcedStyle: symbolic` (default)
12
17
  # # bad
13
18
  # it { is_expected.to have_http_status 200 }
@@ -30,6 +35,19 @@ module RuboCop
30
35
  # it { is_expected.to have_http_status :success }
31
36
  # it { is_expected.to have_http_status :error }
32
37
  #
38
+ # @example `EnforcedStyle: be_status`
39
+ # # bad
40
+ # it { is_expected.to have_http_status :ok }
41
+ # it { is_expected.to have_http_status :not_found }
42
+ # it { is_expected.to have_http_status 200 }
43
+ # it { is_expected.to have_http_status 404 }
44
+ #
45
+ # # good
46
+ # it { is_expected.to be_ok }
47
+ # it { is_expected.to be_not_found }
48
+ # it { is_expected.to have_http_status :success }
49
+ # it { is_expected.to have_http_status :error }
50
+ #
33
51
  class HttpStatus < Base
34
52
  extend AutoCorrector
35
53
  include ConfigurableEnforcedStyle
@@ -41,12 +59,13 @@ module RuboCop
41
59
  PATTERN
42
60
 
43
61
  def on_send(node)
44
- http_status(node) do |ast_node|
45
- checker = checker_class.new(ast_node)
62
+ http_status(node) do |arg|
63
+ checker = checker_class.new(arg)
46
64
  return unless checker.offensive?
47
65
 
48
- add_offense(checker.node, message: checker.message) do |corrector|
49
- corrector.replace(checker.node, checker.preferred_style)
66
+ add_offense(checker.offense_range,
67
+ message: checker.message) do |corrector|
68
+ corrector.replace(checker.offense_range, checker.prefer)
50
69
  end
51
70
  end
52
71
  end
@@ -59,13 +78,16 @@ module RuboCop
59
78
  SymbolicStyleChecker
60
79
  when :numeric
61
80
  NumericStyleChecker
81
+ when :be_status
82
+ BeStatusStyleChecker
62
83
  end
63
84
  end
64
85
 
65
86
  # :nodoc:
66
- class SymbolicStyleChecker
87
+ class StyleCheckerBase
67
88
  MSG = 'Prefer `%<prefer>s` over `%<current>s` ' \
68
89
  'to describe HTTP status code.'
90
+ ALLOWED_STATUSES = %i[error success missing redirect].freeze
69
91
 
70
92
  attr_reader :node
71
93
 
@@ -73,16 +95,36 @@ module RuboCop
73
95
  @node = node
74
96
  end
75
97
 
98
+ def message
99
+ format(MSG, prefer: prefer, current: current)
100
+ end
101
+
102
+ def offense_range
103
+ node
104
+ end
105
+
106
+ def allowed_symbol?
107
+ node.sym_type? && ALLOWED_STATUSES.include?(node.value)
108
+ end
109
+
110
+ def custom_http_status_code?
111
+ node.int_type? &&
112
+ !::Rack::Utils::SYMBOL_TO_STATUS_CODE.value?(node.source.to_i)
113
+ end
114
+ end
115
+
116
+ # :nodoc:
117
+ class SymbolicStyleChecker < StyleCheckerBase
76
118
  def offensive?
77
119
  !node.sym_type? && !custom_http_status_code?
78
120
  end
79
121
 
80
- def message
81
- format(MSG, prefer: preferred_style, current: number.to_s)
122
+ def prefer
123
+ symbol.inspect
82
124
  end
83
125
 
84
- def preferred_style
85
- symbol.inspect
126
+ def current
127
+ number.inspect
86
128
  end
87
129
 
88
130
  private
@@ -94,50 +136,64 @@ module RuboCop
94
136
  def number
95
137
  node.source.to_i
96
138
  end
97
-
98
- def custom_http_status_code?
99
- node.int_type? &&
100
- !::Rack::Utils::SYMBOL_TO_STATUS_CODE.value?(node.source.to_i)
101
- end
102
139
  end
103
140
 
104
141
  # :nodoc:
105
- class NumericStyleChecker
106
- MSG = 'Prefer `%<prefer>s` over `%<current>s` ' \
107
- 'to describe HTTP status code.'
108
-
109
- ALLOWED_STATUSES = %i[error success missing redirect].freeze
110
-
111
- attr_reader :node
112
-
113
- def initialize(node)
114
- @node = node
115
- end
116
-
142
+ class NumericStyleChecker < StyleCheckerBase
117
143
  def offensive?
118
144
  !node.int_type? && !allowed_symbol?
119
145
  end
120
146
 
121
- def message
122
- format(MSG, prefer: preferred_style, current: symbol.inspect)
147
+ def prefer
148
+ number.to_s
123
149
  end
124
150
 
125
- def preferred_style
126
- number.to_s
151
+ def current
152
+ symbol.inspect
127
153
  end
128
154
 
129
155
  private
130
156
 
157
+ def symbol
158
+ node.value
159
+ end
160
+
131
161
  def number
132
162
  ::Rack::Utils::SYMBOL_TO_STATUS_CODE[symbol]
133
163
  end
164
+ end
165
+
166
+ # :nodoc:
167
+ class BeStatusStyleChecker < StyleCheckerBase
168
+ def offensive?
169
+ (!node.sym_type? && !custom_http_status_code?) ||
170
+ (!node.int_type? && !allowed_symbol?)
171
+ end
172
+
173
+ def offense_range
174
+ node.parent
175
+ end
176
+
177
+ def prefer
178
+ if node.sym_type?
179
+ "be_#{node.value}"
180
+ else
181
+ "be_#{symbol}"
182
+ end
183
+ end
184
+
185
+ def current
186
+ offense_range.source
187
+ end
188
+
189
+ private
134
190
 
135
191
  def symbol
136
- node.value
192
+ ::Rack::Utils::SYMBOL_TO_STATUS_CODE.key(number)
137
193
  end
138
194
 
139
- def allowed_symbol?
140
- node.sym_type? && ALLOWED_STATUSES.include?(node.value)
195
+ def number
196
+ node.source.to_i
141
197
  end
142
198
  end
143
199
  end
@@ -96,12 +96,12 @@ module RuboCop
96
96
  # @return [Parser::Source::Range]
97
97
  def remove_range(node)
98
98
  if node.left_sibling
99
- node.loc.expression.with(
100
- begin_pos: node.left_sibling.loc.expression.end_pos
99
+ node.source_range.with(
100
+ begin_pos: node.left_sibling.source_range.end_pos
101
101
  )
102
102
  elsif node.right_sibling
103
- node.loc.expression.with(
104
- end_pos: node.right_sibling.loc.expression.begin_pos
103
+ node.source_range.with(
104
+ end_pos: node.right_sibling.source_range.begin_pos
105
105
  )
106
106
  end
107
107
  end
@@ -13,9 +13,9 @@ module RuboCop
13
13
  # refute_equal(a, b)
14
14
  #
15
15
  # # good
16
- # expect(a).to eq(b)
17
- # expect(a).to(eq(b), "must be equal")
18
- # expect(a).not_to eq(b)
16
+ # expect(b).to eq(a)
17
+ # expect(b).to(eq(a), "must be equal")
18
+ # expect(b).not_to eq(a)
19
19
  #
20
20
  class MinitestAssertions < Base
21
21
  extend AutoCorrector
@@ -43,9 +43,9 @@ module RuboCop
43
43
  def replacement(node, expected, actual, failure_message)
44
44
  runner = node.method?(:assert_equal) ? 'to' : 'not_to'
45
45
  if failure_message.nil?
46
- "expect(#{expected.source}).#{runner} eq(#{actual.source})"
46
+ "expect(#{actual.source}).#{runner} eq(#{expected.source})"
47
47
  else
48
- "expect(#{expected.source}).#{runner}(eq(#{actual.source}), " \
48
+ "expect(#{actual.source}).#{runner}(eq(#{expected.source}), " \
49
49
  "#{failure_message.source})"
50
50
  end
51
51
  end
@@ -0,0 +1,92 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module RSpec
6
+ module Rails
7
+ # Prefer to travel in `before` rather than `around`.
8
+ #
9
+ # @safety
10
+ # This cop is unsafe because the automatic `travel_back` is only run
11
+ # on test cases that are considered as Rails related.
12
+ #
13
+ # And also, this cop's autocorrection is unsafe because the order of
14
+ # execution will change if other steps exist before traveling in
15
+ # `around`.
16
+ #
17
+ # @example
18
+ # # bad
19
+ # around do |example|
20
+ # freeze_time do
21
+ # example.run
22
+ # end
23
+ # end
24
+ #
25
+ # # good
26
+ # before { freeze_time }
27
+ class TravelAround < Base
28
+ extend AutoCorrector
29
+
30
+ MSG = 'Prefer to travel in `before` rather than `around`.'
31
+
32
+ TRAVEL_METHOD_NAMES = %i[
33
+ freeze_time
34
+ travel
35
+ travel_to
36
+ ].to_set.freeze
37
+
38
+ # @!method extract_run_in_travel(node)
39
+ def_node_matcher :extract_run_in_travel, <<~PATTERN
40
+ (block
41
+ $(send nil? TRAVEL_METHOD_NAMES ...)
42
+ (args ...)
43
+ (send _ :run)
44
+ )
45
+ PATTERN
46
+
47
+ # @!method match_around_each?(node)
48
+ def_node_matcher :match_around_each?, <<~PATTERN
49
+ (block
50
+ (send _ :around (sym :each)?)
51
+ ...
52
+ )
53
+ PATTERN
54
+
55
+ def on_block(node)
56
+ run_node = extract_run_in_travel(node)
57
+ return unless run_node
58
+
59
+ around_node = extract_surrounding_around_block(run_node)
60
+ return unless around_node
61
+
62
+ add_offense(node) do |corrector|
63
+ autocorrect(corrector, node, run_node, around_node)
64
+ end
65
+ end
66
+ alias on_numblock on_block
67
+
68
+ private
69
+
70
+ def autocorrect(corrector, node, run_node, around_node)
71
+ corrector.replace(
72
+ node,
73
+ node.body.source
74
+ )
75
+ corrector.insert_before(
76
+ around_node,
77
+ "before { #{run_node.source} }\n\n"
78
+ )
79
+ end
80
+
81
+ # @param node [RuboCop::AST::BlockNode]
82
+ # @return [RuboCop::AST::BlockNode, nil]
83
+ def extract_surrounding_around_block(node)
84
+ node.each_ancestor(:block).find do |ancestor|
85
+ match_around_each?(ancestor)
86
+ end
87
+ end
88
+ end
89
+ end
90
+ end
91
+ end
92
+ end
@@ -80,7 +80,7 @@ module RuboCop
80
80
 
81
81
  def range(node, offending_node)
82
82
  offending_node.loc.dot.with(
83
- end_pos: node.loc.expression.end_pos
83
+ end_pos: node.source_range.end_pos
84
84
  )
85
85
  end
86
86
  end
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module RSpec
6
+ # Remove redundant `around` hook.
7
+ #
8
+ # @example
9
+ # # bad
10
+ # around do |example|
11
+ # example.run
12
+ # end
13
+ #
14
+ # # good
15
+ #
16
+ class RedundantAround < Base
17
+ extend AutoCorrector
18
+
19
+ MSG = 'Remove redundant `around` hook.'
20
+
21
+ RESTRICT_ON_SEND = %i[around].freeze
22
+
23
+ def on_block(node)
24
+ return unless match_redundant_around_hook_block?(node)
25
+
26
+ add_offense(node) do |corrector|
27
+ autocorrect(corrector, node)
28
+ end
29
+ end
30
+ alias on_numblock on_block
31
+
32
+ def on_send(node)
33
+ return unless match_redundant_around_hook_send?(node)
34
+
35
+ add_offense(node) do |corrector|
36
+ autocorrect(corrector, node)
37
+ end
38
+ end
39
+
40
+ private
41
+
42
+ # @!method match_redundant_around_hook_block?(node)
43
+ def_node_matcher :match_redundant_around_hook_block?, <<~PATTERN
44
+ ({block numblock} (send _ :around ...) ... (send _ :run))
45
+ PATTERN
46
+
47
+ # @!method match_redundant_around_hook_send?(node)
48
+ def_node_matcher :match_redundant_around_hook_send?, <<~PATTERN
49
+ (send
50
+ _
51
+ :around
52
+ ...
53
+ (block-pass
54
+ (sym :run)
55
+ )
56
+ )
57
+ PATTERN
58
+
59
+ def autocorrect(corrector, node)
60
+ corrector.remove(node)
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
@@ -43,6 +43,8 @@ module RuboCop
43
43
  # end
44
44
  #
45
45
  class RepeatedExampleGroupBody < Base
46
+ include SkipOrPending
47
+
46
48
  MSG = 'Repeated %<group>s block body on line(s) %<loc>s'
47
49
 
48
50
  # @!method several_example_groups?(node)
@@ -59,11 +61,6 @@ module RuboCop
59
61
  # @!method const_arg(node)
60
62
  def_node_matcher :const_arg, '(block (send _ _ $const ...) ...)'
61
63
 
62
- # @!method skip_or_pending?(node)
63
- def_node_matcher :skip_or_pending?, <<-PATTERN
64
- (block <(send nil? {:skip :pending} ...) ...>)
65
- PATTERN
66
-
67
64
  def on_begin(node)
68
65
  return unless several_example_groups?(node)
69
66
 
@@ -78,7 +75,7 @@ module RuboCop
78
75
  node
79
76
  .children
80
77
  .select { |child| example_group_with_body?(child) }
81
- .reject { |child| skip_or_pending?(child) }
78
+ .reject { |child| skip_or_pending_inside_block?(child) }
82
79
  .group_by { |group| signature_keys(group) }
83
80
  .values
84
81
  .reject(&:one?)
@@ -43,6 +43,8 @@ module RuboCop
43
43
  # end
44
44
  #
45
45
  class RepeatedExampleGroupDescription < Base
46
+ include SkipOrPending
47
+
46
48
  MSG = 'Repeated %<group>s block description on line(s) %<loc>s'
47
49
 
48
50
  # @!method several_example_groups?(node)
@@ -55,11 +57,6 @@ module RuboCop
55
57
  (block (send _ _ $_ $...) ...)
56
58
  PATTERN
57
59
 
58
- # @!method skip_or_pending?(node)
59
- def_node_matcher :skip_or_pending?, <<-PATTERN
60
- (block <(send nil? {:skip :pending}) ...>)
61
- PATTERN
62
-
63
60
  # @!method empty_description?(node)
64
61
  def_node_matcher :empty_description?, '(block (send _ _) ...)'
65
62
 
@@ -77,7 +74,7 @@ module RuboCop
77
74
  node
78
75
  .children
79
76
  .select { |child| example_group?(child) }
80
- .reject { |child| skip_or_pending?(child) }
77
+ .reject { |child| skip_or_pending_inside_block?(child) }
81
78
  .reject { |child| empty_description?(child) }
82
79
  .group_by { |group| doc_string_and_metadata(group) }
83
80
  .values
@@ -56,12 +56,11 @@ module RuboCop
56
56
 
57
57
  # @!method include_examples?(node)
58
58
  def_node_matcher :include_examples?,
59
- send_pattern('#Includes.examples')
59
+ '(send nil? #Includes.examples ...)'
60
60
 
61
61
  # @!method shared_examples_name(node)
62
- def_node_matcher :shared_examples_name, <<-PATTERN
63
- (send _ #Includes.examples $_ ...)
64
- PATTERN
62
+ def_node_matcher :shared_examples_name,
63
+ '(send nil? #Includes.examples $_name ...)'
65
64
 
66
65
  def on_begin(node)
67
66
  return unless several_include_examples?(node)
@@ -23,6 +23,9 @@ module RuboCop
23
23
  # end
24
24
  #
25
25
  class ScatteredSetup < Base
26
+ include RangeHelp
27
+ extend AutoCorrector
28
+
26
29
  MSG = 'Do not define multiple `%<hook_name>s` hooks in the same ' \
27
30
  'example group (also defined on %<lines>s).'
28
31
 
@@ -30,13 +33,11 @@ module RuboCop
30
33
  return unless example_group?(node)
31
34
 
32
35
  repeated_hooks(node).each do |occurrences|
33
- lines = occurrences.map(&:first_line)
34
-
35
36
  occurrences.each do |occurrence|
36
- lines_except_current = lines - [occurrence.first_line]
37
- message = format(MSG, hook_name: occurrences.first.method_name,
38
- lines: lines_msg(lines_except_current))
39
- add_offense(occurrence, message: message)
37
+ message = message(occurrences, occurrence)
38
+ add_offense(occurrence, message: message) do |corrector|
39
+ autocorrect(corrector, occurrences.first, occurrence)
40
+ end
40
41
  end
41
42
  end
42
43
  end
@@ -63,6 +64,22 @@ module RuboCop
63
64
  "lines #{numbers.join(', ')}"
64
65
  end
65
66
  end
67
+
68
+ def message(occurrences, occurrence)
69
+ lines = occurrences.map(&:first_line)
70
+ lines_except_current = lines - [occurrence.first_line]
71
+ format(MSG, hook_name: occurrences.first.method_name,
72
+ lines: lines_msg(lines_except_current))
73
+ end
74
+
75
+ def autocorrect(corrector, first_occurrence, occurrence)
76
+ return if first_occurrence == occurrence || !first_occurrence.body
77
+
78
+ corrector.insert_after(first_occurrence.body,
79
+ "\n#{occurrence.body.source}")
80
+ corrector.remove(range_by_whole_lines(occurrence.source_range,
81
+ include_final_newline: true))
82
+ end
66
83
  end
67
84
  end
68
85
  end
@@ -57,27 +57,26 @@ module RuboCop
57
57
  MSG_CONTEXT = "Use `shared_context` when you don't define examples."
58
58
 
59
59
  # @!method examples?(node)
60
- def_node_search :examples?,
61
- send_pattern('{#Includes.examples #Examples.all}')
60
+ def_node_search :examples?, <<~PATTERN
61
+ (send nil? {#Includes.examples #Examples.all} ...)
62
+ PATTERN
62
63
 
63
64
  # @!method context?(node)
64
65
  def_node_search :context?, <<-PATTERN
65
- (
66
- send #rspec? {
67
- #Subjects.all
68
- #Helpers.all
69
- #Includes.context
70
- #Hooks.all
71
- } ...
66
+ (send nil?
67
+ {#Subjects.all #Helpers.all #Includes.context #Hooks.all} ...
72
68
  )
73
69
  PATTERN
74
70
 
75
71
  # @!method shared_context(node)
76
- def_node_matcher :shared_context,
77
- block_pattern('#SharedGroups.context')
72
+ def_node_matcher :shared_context, <<~PATTERN
73
+ (block (send #rspec? #SharedGroups.context ...) ...)
74
+ PATTERN
75
+
78
76
  # @!method shared_example(node)
79
- def_node_matcher :shared_example,
80
- block_pattern('#SharedGroups.examples')
77
+ def_node_matcher :shared_example, <<~PATTERN
78
+ (block (send #rspec? #SharedGroups.examples ...) ...)
79
+ PATTERN
81
80
 
82
81
  def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler
83
82
  context_with_only_examples(node) do
@@ -24,10 +24,12 @@ module RuboCop
24
24
  extend AutoCorrector
25
25
 
26
26
  # @!method shared_examples(node)
27
- def_node_matcher :shared_examples,
28
- send_pattern(
29
- '{#SharedGroups.all #Includes.all}'
30
- )
27
+ def_node_matcher :shared_examples, <<~PATTERN
28
+ {
29
+ (send #rspec? #SharedGroups.all ...)
30
+ (send nil? #Includes.all ...)
31
+ }
32
+ PATTERN
31
33
 
32
34
  def on_send(node)
33
35
  shared_examples(node) do
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module RSpec
6
+ # Checks for passing a block to `skip` within examples.
7
+ #
8
+ # @example
9
+ # # bad
10
+ # it 'does something' do
11
+ # skip 'not yet implemented' do
12
+ # do_something
13
+ # end
14
+ # end
15
+ #
16
+ # # good
17
+ # it 'does something' do
18
+ # skip 'not yet implemented'
19
+ # do_something
20
+ # end
21
+ #
22
+ # # good - when outside example
23
+ # skip 'not yet implemented' do
24
+ # end
25
+ #
26
+ class SkipBlockInsideExample < Base
27
+ MSG = "Don't pass a block to `skip` inside examples."
28
+
29
+ def on_block(node)
30
+ return unless node.method?(:skip)
31
+ return unless inside_example?(node)
32
+
33
+ add_offense(node)
34
+ end
35
+
36
+ alias on_numblock on_block
37
+
38
+ private
39
+
40
+ def inside_example?(node)
41
+ node.each_ancestor(:block).any? { |ancestor| example?(ancestor) }
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end