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
@@ -61,10 +61,9 @@ module RuboCop
61
61
  PATTERN
62
62
 
63
63
  # @!method focused_block?(node)
64
- def_node_matcher :focused_block?,
65
- send_pattern(<<~PATTERN)
66
- {#ExampleGroups.focused #Examples.focused}
67
- PATTERN
64
+ def_node_matcher :focused_block?, <<~PATTERN
65
+ (send #rspec? {#ExampleGroups.focused #Examples.focused} ...)
66
+ PATTERN
68
67
 
69
68
  def on_send(node)
70
69
  focus_metadata(node) do |focus|
@@ -88,7 +87,7 @@ module RuboCop
88
87
 
89
88
  def with_surrounding(focus)
90
89
  range_with_space =
91
- range_with_surrounding_space(focus.loc.expression, side: :left)
90
+ range_with_surrounding_space(focus.source_range, side: :left)
92
91
 
93
92
  range_with_surrounding_comma(range_with_space, :left)
94
93
  end
@@ -83,8 +83,7 @@ module RuboCop
83
83
  style_detected(scope_name)
84
84
  msg = explicit_message(scope_name)
85
85
  add_offense(method_send, message: msg) do |corrector|
86
- scope = implicit_style? ? '' : "(#{style.inspect})"
87
- corrector.replace(argument_range(method_send), scope)
86
+ autocorrect(corrector, node, method_send)
88
87
  end
89
88
  end
90
89
  end
@@ -93,6 +92,13 @@ module RuboCop
93
92
 
94
93
  private
95
94
 
95
+ def autocorrect(corrector, _node, method_send)
96
+ scope = implicit_style? ? '' : "(#{style.inspect})"
97
+ corrector.replace(
98
+ LocationHelp.arguments_with_whitespace(method_send), scope
99
+ )
100
+ end
101
+
96
102
  def check_implicit(method_send)
97
103
  style_detected(:implicit)
98
104
  return if implicit_style?
@@ -100,7 +106,10 @@ module RuboCop
100
106
  msg = explicit_message(nil)
101
107
  add_offense(method_send.loc.selector, message: msg) do |corrector|
102
108
  scope = "(#{style.inspect})"
103
- corrector.replace(argument_range(method_send), scope)
109
+ corrector.replace(
110
+ LocationHelp.arguments_with_whitespace(method_send),
111
+ scope
112
+ )
104
113
  end
105
114
  end
106
115
 
@@ -119,12 +128,6 @@ module RuboCop
119
128
  def hook(node, &block)
120
129
  scoped_hook(node, &block) || unscoped_hook(node, &block)
121
130
  end
122
-
123
- def argument_range(send_node)
124
- send_node.loc.selector.end.with(
125
- end_pos: send_node.loc.expression.end_pos
126
- )
127
- end
128
131
  end
129
132
  end
130
133
  end
@@ -30,9 +30,11 @@ module RuboCop
30
30
  # @!method example_or_group?(node)
31
31
  def_node_matcher :example_or_group?, <<-PATTERN
32
32
  {
33
- #{block_pattern('{#ExampleGroups.all #Examples.all}')}
34
- #{numblock_pattern('{#ExampleGroups.all #Examples.all}')}
35
- #{send_pattern('#Includes.examples')}
33
+ ({block numblock} {
34
+ (send #rspec? #ExampleGroups.all ...)
35
+ (send nil? #Examples.all ...)
36
+ } ...)
37
+ (send nil? #Includes.examples ...)
36
38
  }
37
39
  PATTERN
38
40
 
@@ -0,0 +1,76 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module RSpec
6
+ # Do not set up test data using indexes (e.g., `item_1`, `item_2`).
7
+ #
8
+ # It makes reading the test harder because it's not clear what exactly
9
+ # is tested by this particular example.
10
+ #
11
+ # @example `Max: 1 (default)`
12
+ # # bad
13
+ # let(:item_1) { create(:item) }
14
+ # let(:item_2) { create(:item) }
15
+ #
16
+ # let(:item1) { create(:item) }
17
+ # let(:item2) { create(:item) }
18
+ #
19
+ # # good
20
+ #
21
+ # let(:visible_item) { create(:item, visible: true) }
22
+ # let(:invisible_item) { create(:item, visible: false) }
23
+ #
24
+ # @example `Max: 2`
25
+ # # bad
26
+ # let(:item_1) { create(:item) }
27
+ # let(:item_2) { create(:item) }
28
+ # let(:item_3) { create(:item) }
29
+ #
30
+ # # good
31
+ # let(:item_1) { create(:item) }
32
+ # let(:item_2) { create(:item) }
33
+ #
34
+ class IndexedLet < Base
35
+ MSG = 'This `let` statement uses index in its name. Please give it ' \
36
+ 'a meaningful name.'
37
+
38
+ # @!method let_name(node)
39
+ def_node_matcher :let_name, <<~PATTERN
40
+ {
41
+ (block (send nil? #Helpers.all ({str sym} $_) ...) ...)
42
+ (send nil? #Helpers.all ({str sym} $_) block_pass)
43
+ }
44
+ PATTERN
45
+
46
+ def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler
47
+ return unless spec_group?(node)
48
+
49
+ children = node.body&.child_nodes
50
+ return unless children
51
+
52
+ filter_indexed_lets(children).each do |let_node|
53
+ add_offense(let_node)
54
+ end
55
+ end
56
+
57
+ private
58
+
59
+ INDEX_REGEX = /_?\d+/.freeze
60
+
61
+ def filter_indexed_lets(candidates)
62
+ candidates
63
+ .filter { |node| indexed_let?(node) }
64
+ .group_by { |node| let_name(node).to_s.gsub(INDEX_REGEX, '') }
65
+ .values
66
+ .filter { |lets| lets.length > cop_config['Max'] }
67
+ .flatten
68
+ end
69
+
70
+ def indexed_let?(node)
71
+ let?(node) && INDEX_REGEX.match?(let_name(node))
72
+ end
73
+ end
74
+ end
75
+ end
76
+ end
@@ -38,16 +38,16 @@ module RuboCop
38
38
  # @!method example_or_group?(node)
39
39
  def_node_matcher :example_or_group?, <<-PATTERN
40
40
  {
41
- #{block_pattern('{#ExampleGroups.all #Examples.all}')}
42
- #{send_pattern('#Includes.examples')}
41
+ (block (send nil? {#ExampleGroups.all #Examples.all} ...) ...)
42
+ (send nil? #Includes.examples ...)
43
43
  }
44
44
  PATTERN
45
45
 
46
46
  # @!method include_examples?(node)
47
47
  def_node_matcher :include_examples?, <<~PATTERN
48
48
  {
49
- #{block_pattern(':include_examples')}
50
- #{send_pattern(':include_examples')}
49
+ (block (send nil? :include_examples ...) ...)
50
+ (send nil? :include_examples ...)
51
51
  }
52
52
  PATTERN
53
53
 
@@ -29,14 +29,12 @@ module RuboCop
29
29
  MSG = 'Do not use `let!` to setup objects not referenced in tests.'
30
30
 
31
31
  # @!method example_or_shared_group_or_including?(node)
32
- def_node_matcher :example_or_shared_group_or_including?,
33
- block_pattern(<<~PATTERN)
34
- {
35
- #SharedGroups.all
36
- #ExampleGroups.all
37
- #Includes.all
38
- }
39
- PATTERN
32
+ def_node_matcher :example_or_shared_group_or_including?, <<~PATTERN
33
+ (block {
34
+ (send #rspec? {#SharedGroups.all #ExampleGroups.all} ...)
35
+ (send nil? #Includes.all ...)
36
+ } ...)
37
+ PATTERN
40
38
 
41
39
  # @!method let_bang(node)
42
40
  def_node_matcher :let_bang, <<-PATTERN
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module RSpec
6
+ # Checks where `match_array` is used.
7
+ #
8
+ # This cop checks for the following:
9
+ # - Prefer `contain_exactly` when matching an array with values.
10
+ # - Prefer `eq` when using `match_array` with an empty array literal.
11
+ #
12
+ # @example
13
+ # # bad
14
+ # it { is_expected.to match_array([content1, content2]) }
15
+ #
16
+ # # good
17
+ # it { is_expected.to contain_exactly(content1, content2) }
18
+ #
19
+ # # good
20
+ # it { is_expected.to match_array([content] + array) }
21
+ #
22
+ # # good
23
+ # it { is_expected.to match_array(%w(tremble in fear foolish mortals)) }
24
+ #
25
+ class MatchArray < Base
26
+ extend AutoCorrector
27
+
28
+ MSG = 'Prefer `contain_exactly` when matching an array literal.'
29
+ RESTRICT_ON_SEND = %i[match_array].freeze
30
+
31
+ # @!method match_array_with_empty_array?(node)
32
+ def_node_matcher :match_array_with_empty_array?, <<~PATTERN
33
+ (send nil? :match_array (array))
34
+ PATTERN
35
+
36
+ def on_send(node)
37
+ return unless node.first_argument.array_type?
38
+ return if match_array_with_empty_array?(node)
39
+
40
+ check_populated_array(node)
41
+ end
42
+
43
+ private
44
+
45
+ def check_populated_array(node)
46
+ return if node.first_argument.percent_literal?
47
+
48
+ add_offense(node) do |corrector|
49
+ array_contents = node.arguments.flat_map(&:to_a)
50
+ corrector.replace(
51
+ node,
52
+ "contain_exactly(#{array_contents.map(&:source).join(', ')})"
53
+ )
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
@@ -25,8 +25,7 @@ module RuboCop
25
25
 
26
26
  def missing_separating_line(node)
27
27
  line = final_end_line = final_end_location(node).line
28
-
29
- while comment_line?(processed_source[line])
28
+ while processed_source.line_with_comment?(line + 1)
30
29
  line += 1
31
30
  comment = processed_source.comment_at_line(line)
32
31
  if DirectiveComment.new(comment).enabled?
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module RSpec
6
+ # Helper methods to location.
7
+ module LocationHelp
8
+ module_function
9
+
10
+ # @param node [RuboCop::AST::SendNode]
11
+ # @return [Parser::Source::Range]
12
+ # @example
13
+ # foo 1, 2
14
+ # ^^^^^
15
+ def arguments_with_whitespace(node)
16
+ node.loc.selector.end.with(
17
+ end_pos: node.source_range.end_pos
18
+ )
19
+ end
20
+
21
+ # @param node [RuboCop::AST::SendNode]
22
+ # @return [Parser::Source::Range]
23
+ # @example
24
+ # foo { bar }
25
+ # ^^^^^^^^
26
+ def block_with_whitespace(node)
27
+ return unless (parent = node.parent)
28
+ return unless parent.block_type?
29
+
30
+ node.source_range.end.with(
31
+ end_pos: parent.source_range.end_pos
32
+ )
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -10,13 +10,29 @@ module RuboCop
10
10
  # @!method skipped_in_metadata?(node)
11
11
  def_node_matcher :skipped_in_metadata?, <<-PATTERN
12
12
  {
13
- (send _ _ <#skip_or_pending? ...>)
14
- (send _ _ ... (hash <(pair #skip_or_pending? { true str dstr }) ...>))
13
+ (send _ _ <(sym {:skip :pending}) ...>)
14
+ (send _ _ ... (hash <(pair (sym {:skip :pending}) { true str dstr }) ...>))
15
15
  }
16
16
  PATTERN
17
17
 
18
- # @!method skip_or_pending?(node)
19
- def_node_matcher :skip_or_pending?, '{(sym :skip) (sym :pending)}'
18
+ # @!method skip_or_pending_inside_block?(node)
19
+ # Match skip/pending statements inside a block (e.g. `context`)
20
+ #
21
+ # @example source that matches
22
+ # context 'when color is blue' do
23
+ # skip 'not implemented yet'
24
+ # pending 'not implemented yet'
25
+ # end
26
+ #
27
+ # @example source that does not match
28
+ # skip 'not implemented yet'
29
+ # pending 'not implemented yet'
30
+ #
31
+ # @param node [RuboCop::AST::Node]
32
+ # @return [Array<RuboCop::AST::Node>] matching nodes
33
+ def_node_matcher :skip_or_pending_inside_block?, <<-PATTERN
34
+ (block <(send nil? {:skip :pending} ...) ...>)
35
+ PATTERN
20
36
  end
21
37
  end
22
38
  end
@@ -78,7 +78,8 @@ module RuboCop
78
78
  PATTERN
79
79
 
80
80
  # @!method expect?(node)
81
- def_node_matcher :expect?, send_pattern('#Expectations.all')
81
+ def_node_matcher :expect?, '(send nil? #Expectations.all ...)'
82
+
82
83
  # @!method aggregate_failures_block?(node)
83
84
  def_node_matcher :aggregate_failures_block?, <<-PATTERN
84
85
  (block (send nil? :aggregate_failures ...) ...)
@@ -82,12 +82,14 @@ module RuboCop
82
82
  MSG = 'Name your test subject if you need to reference it explicitly.'
83
83
 
84
84
  # @!method example_or_hook_block?(node)
85
- def_node_matcher :example_or_hook_block?,
86
- block_pattern('{#Examples.all #Hooks.all}')
85
+ def_node_matcher :example_or_hook_block?, <<~PATTERN
86
+ (block (send nil? {#Examples.all #Hooks.all} ...) ...)
87
+ PATTERN
87
88
 
88
89
  # @!method shared_example?(node)
89
- def_node_matcher :shared_example?,
90
- block_pattern('#SharedGroups.examples')
90
+ def_node_matcher :shared_example?, <<~PATTERN
91
+ (block (send #rspec? #SharedGroups.examples ...) ...)
92
+ PATTERN
91
93
 
92
94
  # @!method subject_usage(node)
93
95
  def_node_search :subject_usage, '$(send nil? :subject)'
@@ -65,10 +65,7 @@ module RuboCop
65
65
  # @param [RuboCop::AST::Node] node
66
66
  # @return [Boolean]
67
67
  def_node_matcher :regular_or_focused_example?, <<~PATTERN
68
- {
69
- #{block_pattern('{#Examples.regular | #Examples.focused}')}
70
- #{numblock_pattern('{#Examples.regular | #Examples.focused}')}
71
- }
68
+ ({block numblock} (send nil? {#Examples.regular #Examples.focused} ...) ...)
72
69
  PATTERN
73
70
 
74
71
  # @!method includes_expectation?(node)
@@ -76,7 +73,7 @@ module RuboCop
76
73
  # @return [Boolean]
77
74
  def_node_search :includes_expectation?, <<~PATTERN
78
75
  {
79
- #{send_pattern('#Expectations.all')}
76
+ (send nil? #Expectations.all ...)
80
77
  (send nil? `#matches_allowed_pattern? ...)
81
78
  }
82
79
  PATTERN
@@ -26,7 +26,9 @@ module RuboCop
26
26
  MSG = '`%<name>s` is already defined.'
27
27
 
28
28
  # @!method setup?(node)
29
- def_node_matcher :setup?, block_pattern('{#Helpers.all #Subjects.all}')
29
+ def_node_matcher :setup?, <<~PATTERN
30
+ (block (send nil? {#Helpers.all #Subjects.all} ...) ...)
31
+ PATTERN
30
32
 
31
33
  # @!method first_argument_name(node)
32
34
  def_node_matcher :first_argument_name, '(send _ _ ({str sym} $_))'
@@ -38,20 +38,20 @@ module RuboCop
38
38
  MSG = 'Pending spec found.'
39
39
 
40
40
  # @!method skippable?(node)
41
- def_node_matcher :skippable?,
42
- send_pattern(<<~PATTERN)
43
- {#ExampleGroups.regular #Examples.regular}
44
- PATTERN
41
+ def_node_matcher :skippable?, <<~PATTERN
42
+ {
43
+ (send #rspec? #ExampleGroups.regular ...)
44
+ (send nil? #Examples.regular ...)
45
+ }
46
+ PATTERN
45
47
 
46
48
  # @!method pending_block?(node)
47
- def_node_matcher :pending_block?,
48
- send_pattern(<<~PATTERN)
49
- {
50
- #ExampleGroups.skipped
51
- #Examples.skipped
52
- #Examples.pending
53
- }
54
- PATTERN
49
+ def_node_matcher :pending_block?, <<~PATTERN
50
+ {
51
+ (send #rspec? #ExampleGroups.skipped ...)
52
+ (send nil? {#Examples.skipped #Examples.pending} ...)
53
+ }
54
+ PATTERN
55
55
 
56
56
  def on_send(node)
57
57
  return unless pending_block?(node) || skipped?(node)
@@ -59,61 +59,99 @@ module RuboCop
59
59
  class PendingWithoutReason < Base
60
60
  MSG = 'Give the reason for pending or skip.'
61
61
 
62
- # @!method pending_by_example_method?(node)
63
- def_node_matcher :pending_by_example_method?, block_pattern(<<~PATTERN)
64
- #Examples.pending
62
+ # @!method skipped_in_example?(node)
63
+ def_node_matcher :skipped_in_example?, <<~PATTERN
64
+ {
65
+ (send nil? ${#Examples.skipped #Examples.pending})
66
+ (block (send nil? ${#Examples.skipped}) ...)
67
+ (numblock (send nil? ${#Examples.skipped}) ...)
68
+ }
65
69
  PATTERN
66
70
 
67
- # @!method pending_by_metadata_without_reason?(node)
68
- def_node_matcher :pending_by_metadata_without_reason?, <<~PATTERN
69
- (send #rspec? {#ExampleGroups.all #Examples.all} ... {<(sym :pending) ...> (hash <(pair (sym :pending) true) ...>)})
71
+ # @!method skipped_by_example_method?(node)
72
+ def_node_matcher :skipped_by_example_method?, <<~PATTERN
73
+ (send nil? ${#Examples.skipped #Examples.pending})
70
74
  PATTERN
71
75
 
72
- # @!method skipped_by_example_method?(node)
73
- def_node_matcher :skipped_by_example_method?, block_pattern(<<~PATTERN)
74
- #Examples.skipped
76
+ # @!method skipped_by_example_method_with_block?(node)
77
+ def_node_matcher :skipped_by_example_method_with_block?, <<~PATTERN
78
+ ({block numblock} (send nil? ${#Examples.skipped #Examples.pending} ...) ...)
79
+ PATTERN
80
+
81
+ # @!method metadata_without_reason?(node)
82
+ def_node_matcher :metadata_without_reason?, <<~PATTERN
83
+ (send #rspec?
84
+ {#ExampleGroups.all #Examples.all} ...
85
+ {
86
+ <(sym ${:pending :skip}) ...>
87
+ (hash <(pair (sym ${:pending :skip}) true) ...>)
88
+ }
89
+ )
75
90
  PATTERN
76
91
 
77
92
  # @!method skipped_by_example_group_method?(node)
78
- def_node_matcher(
79
- :skipped_by_example_group_method?,
80
- block_pattern(<<~PATTERN)
81
- #ExampleGroups.skipped
82
- PATTERN
83
- )
84
-
85
- # @!method skipped_by_metadata_without_reason?(node)
86
- def_node_matcher :skipped_by_metadata_without_reason?, <<~PATTERN
87
- (send #rspec? {#ExampleGroups.all #Examples.all} ... {<(sym :skip) ...> (hash <(pair (sym :skip) true) ...>)})
93
+ def_node_matcher :skipped_by_example_group_method?, <<~PATTERN
94
+ (send #rspec? ${#ExampleGroups.skipped} ...)
88
95
  PATTERN
89
96
 
90
- # @!method without_reason?(node)
91
- def_node_matcher :without_reason?, <<~PATTERN
92
- (send nil? ${:pending :skip})
97
+ # @!method pending_step_without_reason?(node)
98
+ def_node_matcher :pending_step_without_reason?, <<~PATTERN
99
+ (send nil? {:skip :pending})
93
100
  PATTERN
94
101
 
95
102
  def on_send(node)
96
- if pending_without_reason?(node)
97
- add_offense(node, message: 'Give the reason for pending.')
98
- elsif skipped_without_reason?(node)
99
- add_offense(node, message: 'Give the reason for skip.')
100
- elsif without_reason?(node) && example?(node.parent)
101
- add_offense(node,
102
- message: "Give the reason for #{node.method_name}.")
103
+ on_pending_by_metadata(node)
104
+ return unless (parent = parent_node(node))
105
+
106
+ if example_group?(parent) || block_node_example_group?(node)
107
+ on_skipped_by_example_method(node)
108
+ on_skipped_by_example_group_method(node)
109
+ elsif example?(parent)
110
+ on_skipped_by_in_example_method(node)
103
111
  end
104
112
  end
105
113
 
106
114
  private
107
115
 
108
- def pending_without_reason?(node)
109
- pending_by_example_method?(node.block_node) ||
110
- pending_by_metadata_without_reason?(node)
116
+ def parent_node(node)
117
+ node_or_block = node.block_node || node
118
+ return unless (parent = node_or_block.parent)
119
+
120
+ parent.begin_type? && parent.parent ? parent.parent : parent
121
+ end
122
+
123
+ def block_node_example_group?(node)
124
+ node.block_node &&
125
+ example_group?(node.block_node) &&
126
+ explicit_rspec?(node.receiver)
127
+ end
128
+
129
+ def on_skipped_by_in_example_method(node)
130
+ skipped_in_example?(node) do |pending|
131
+ add_offense(node, message: "Give the reason for #{pending}.")
132
+ end
133
+ end
134
+
135
+ def on_pending_by_metadata(node)
136
+ metadata_without_reason?(node) do |pending|
137
+ add_offense(node, message: "Give the reason for #{pending}.")
138
+ end
111
139
  end
112
140
 
113
- def skipped_without_reason?(node)
114
- skipped_by_example_group_method?(node.block_node) ||
115
- skipped_by_example_method?(node.block_node) ||
116
- skipped_by_metadata_without_reason?(node)
141
+ def on_skipped_by_example_method(node)
142
+ skipped_by_example_method?(node) do |pending|
143
+ add_offense(node, message: "Give the reason for #{pending}.")
144
+ end
145
+
146
+ skipped_by_example_method_with_block?(node.parent) do |pending|
147
+ add_offense(node, message: "Give the reason for #{pending}.")
148
+ end
149
+ end
150
+
151
+ def on_skipped_by_example_group_method(node)
152
+ skipped_by_example_group_method?(node) do
153
+ add_offense(node, message: 'Give the reason for skip.')
154
+ end
117
155
  end
118
156
  end
119
157
  end
@@ -84,22 +84,22 @@ module RuboCop
84
84
 
85
85
  def remove_predicate(corrector, predicate)
86
86
  range = predicate.loc.dot.with(
87
- end_pos: predicate.loc.expression.end_pos
87
+ end_pos: predicate.source_range.end_pos
88
88
  )
89
89
 
90
90
  corrector.remove(range)
91
91
 
92
- block_range = block_loc(predicate)
92
+ block_range = LocationHelp.block_with_whitespace(predicate)
93
93
  corrector.remove(block_range) if block_range
94
94
  end
95
95
 
96
96
  def rewrite_matcher(corrector, predicate, matcher)
97
- args = args_loc(predicate).source
98
- block_loc = block_loc(predicate)
97
+ args = LocationHelp.arguments_with_whitespace(predicate).source
98
+ block_loc = LocationHelp.block_with_whitespace(predicate)
99
99
  block = block_loc ? block_loc.source : ''
100
100
 
101
101
  corrector.replace(
102
- matcher.loc.expression,
102
+ matcher,
103
103
  to_predicate_matcher(predicate.method_name) + args + block
104
104
  )
105
105
  end
@@ -214,20 +214,19 @@ module RuboCop
214
214
 
215
215
  def corrector_explicit(corrector, to_node, actual, matcher, block_child)
216
216
  replacement_matcher = replacement_matcher(to_node)
217
- corrector.replace(matcher.loc.expression, replacement_matcher)
217
+ corrector.replace(matcher, replacement_matcher)
218
218
  move_predicate(corrector, actual, matcher, block_child)
219
219
  corrector.replace(to_node.loc.selector, 'to')
220
220
  end
221
221
 
222
222
  def move_predicate(corrector, actual, matcher, block_child)
223
223
  predicate = to_predicate_method(matcher.method_name)
224
- args = args_loc(matcher).source
225
- block_loc = block_loc(block_child)
224
+ args = LocationHelp.arguments_with_whitespace(matcher).source
225
+ block_loc = LocationHelp.block_with_whitespace(block_child)
226
226
  block = block_loc ? block_loc.source : ''
227
227
 
228
228
  corrector.remove(block_loc) if block_loc
229
- corrector.insert_after(actual.loc.expression,
230
- ".#{predicate}" + args + block)
229
+ corrector.insert_after(actual, ".#{predicate}" + args + block)
231
230
  end
232
231
 
233
232
  # rubocop:disable Metrics/MethodLength
@@ -332,31 +331,6 @@ module RuboCop
332
331
  def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler
333
332
  check_explicit(node) if style == :explicit
334
333
  end
335
-
336
- private
337
-
338
- # returns args location with whitespace
339
- # @example
340
- # foo 1, 2
341
- # ^^^^^
342
- def args_loc(send_node)
343
- send_node.loc.selector.end.with(
344
- end_pos: send_node.loc.expression.end_pos
345
- )
346
- end
347
-
348
- # returns block location with whitespace
349
- # @example
350
- # foo { bar }
351
- # ^^^^^^^^
352
- def block_loc(send_node)
353
- parent = send_node.parent
354
- return unless parent.block_type?
355
-
356
- send_node.loc.expression.end.with(
357
- end_pos: parent.loc.expression.end_pos
358
- )
359
- end
360
334
  end
361
335
  end
362
336
  end