rubocop-rspec 2.0.0.pre → 2.3.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 (112) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +39 -2
  3. data/README.md +7 -3
  4. data/config/default.yml +159 -73
  5. data/lib/rubocop-rspec.rb +8 -8
  6. data/lib/rubocop/cop/rspec/align_left_let_brace.rb +7 -3
  7. data/lib/rubocop/cop/rspec/align_right_let_brace.rb +7 -3
  8. data/lib/rubocop/cop/rspec/any_instance.rb +6 -10
  9. data/lib/rubocop/cop/rspec/around_block.rb +3 -1
  10. data/lib/rubocop/cop/rspec/base.rb +6 -55
  11. data/lib/rubocop/cop/rspec/be.rb +3 -2
  12. data/lib/rubocop/cop/rspec/be_eql.rb +2 -0
  13. data/lib/rubocop/cop/rspec/before_after_all.rb +6 -3
  14. data/lib/rubocop/cop/rspec/capybara/current_path_expectation.rb +7 -2
  15. data/lib/rubocop/cop/rspec/capybara/feature_methods.rb +6 -2
  16. data/lib/rubocop/cop/rspec/capybara/visibility_matcher.rb +4 -0
  17. data/lib/rubocop/cop/rspec/context_method.rb +1 -0
  18. data/lib/rubocop/cop/rspec/context_wording.rb +7 -1
  19. data/lib/rubocop/cop/rspec/describe_class.rb +5 -2
  20. data/lib/rubocop/cop/rspec/describe_method.rb +3 -2
  21. data/lib/rubocop/cop/rspec/describe_symbol.rb +2 -0
  22. data/lib/rubocop/cop/rspec/described_class.rb +6 -2
  23. data/lib/rubocop/cop/rspec/described_class_module_wrapping.rb +3 -3
  24. data/lib/rubocop/cop/rspec/dialect.rb +2 -1
  25. data/lib/rubocop/cop/rspec/empty_example_group.rb +6 -45
  26. data/lib/rubocop/cop/rspec/empty_hook.rb +2 -1
  27. data/lib/rubocop/cop/rspec/empty_line_after_example.rb +1 -1
  28. data/lib/rubocop/cop/rspec/empty_line_after_example_group.rb +1 -1
  29. data/lib/rubocop/cop/rspec/empty_line_after_final_let.rb +1 -1
  30. data/lib/rubocop/cop/rspec/empty_line_after_hook.rb +1 -1
  31. data/lib/rubocop/cop/rspec/empty_line_after_subject.rb +2 -2
  32. data/lib/rubocop/cop/rspec/example_length.rb +26 -12
  33. data/lib/rubocop/cop/rspec/example_without_description.rb +1 -0
  34. data/lib/rubocop/cop/rspec/example_wording.rb +1 -0
  35. data/lib/rubocop/cop/rspec/expect_actual.rb +3 -1
  36. data/lib/rubocop/cop/rspec/expect_change.rb +3 -0
  37. data/lib/rubocop/cop/rspec/expect_in_hook.rb +2 -1
  38. data/lib/rubocop/cop/rspec/expect_output.rb +1 -1
  39. data/lib/rubocop/cop/rspec/factory_bot/attribute_defined_statically.rb +3 -0
  40. data/lib/rubocop/cop/rspec/factory_bot/create_list.rb +4 -0
  41. data/lib/rubocop/cop/rspec/factory_bot/factory_class_name.rb +2 -0
  42. data/lib/rubocop/cop/rspec/file_path.rb +26 -17
  43. data/lib/rubocop/cop/rspec/focus.rb +46 -8
  44. data/lib/rubocop/cop/rspec/hook_argument.rb +4 -4
  45. data/lib/rubocop/cop/rspec/hooks_before_examples.rb +3 -2
  46. data/lib/rubocop/cop/rspec/implicit_block_expectation.rb +4 -0
  47. data/lib/rubocop/cop/rspec/implicit_expect.rb +2 -1
  48. data/lib/rubocop/cop/rspec/implicit_subject.rb +2 -0
  49. data/lib/rubocop/cop/rspec/instance_spy.rb +3 -1
  50. data/lib/rubocop/cop/rspec/instance_variable.rb +5 -1
  51. data/lib/rubocop/cop/rspec/it_behaves_like.rb +3 -1
  52. data/lib/rubocop/cop/rspec/iterated_expectation.rb +3 -1
  53. data/lib/rubocop/cop/rspec/let_before_examples.rb +3 -2
  54. data/lib/rubocop/cop/rspec/let_setup.rb +10 -4
  55. data/lib/rubocop/cop/rspec/message_chain.rb +4 -10
  56. data/lib/rubocop/cop/rspec/message_expectation.rb +3 -0
  57. data/lib/rubocop/cop/rspec/message_spies.rb +5 -3
  58. data/lib/rubocop/cop/rspec/mixin/comments_help.rb +38 -0
  59. data/lib/rubocop/cop/rspec/mixin/empty_line_separation.rb +51 -0
  60. data/lib/rubocop/cop/rspec/mixin/final_end_location.rb +19 -0
  61. data/lib/rubocop/cop/rspec/mixin/top_level_group.rb +54 -0
  62. data/lib/rubocop/cop/rspec/mixin/variable.rb +21 -0
  63. data/lib/rubocop/cop/rspec/multiple_describes.rb +2 -3
  64. data/lib/rubocop/cop/rspec/multiple_expectations.rb +4 -1
  65. data/lib/rubocop/cop/rspec/multiple_memoized_helpers.rb +3 -1
  66. data/lib/rubocop/cop/rspec/named_subject.rb +11 -12
  67. data/lib/rubocop/cop/rspec/nested_groups.rb +1 -1
  68. data/lib/rubocop/cop/rspec/not_to_not.rb +2 -0
  69. data/lib/rubocop/cop/rspec/overwriting_setup.rb +4 -1
  70. data/lib/rubocop/cop/rspec/pending.rb +17 -5
  71. data/lib/rubocop/cop/rspec/predicate_matcher.rb +8 -3
  72. data/lib/rubocop/cop/rspec/rails/http_status.rb +2 -0
  73. data/lib/rubocop/cop/rspec/receive_counts.rb +4 -0
  74. data/lib/rubocop/cop/rspec/receive_never.rb +2 -0
  75. data/lib/rubocop/cop/rspec/repeated_example_group_body.rb +8 -1
  76. data/lib/rubocop/cop/rspec/repeated_example_group_description.rb +4 -0
  77. data/lib/rubocop/cop/rspec/repeated_include_example.rb +6 -2
  78. data/lib/rubocop/cop/rspec/return_from_stub.rb +6 -0
  79. data/lib/rubocop/cop/rspec/scattered_setup.rb +1 -1
  80. data/lib/rubocop/cop/rspec/shared_context.rb +22 -11
  81. data/lib/rubocop/cop/rspec/shared_examples.rb +4 -1
  82. data/lib/rubocop/cop/rspec/single_argument_message_chain.rb +4 -1
  83. data/lib/rubocop/cop/rspec/stubbed_mock.rb +2 -1
  84. data/lib/rubocop/cop/rspec/subject_stub.rb +17 -6
  85. data/lib/rubocop/cop/rspec/unspecified_exception.rb +2 -0
  86. data/lib/rubocop/cop/rspec/variable_definition.rb +1 -1
  87. data/lib/rubocop/cop/rspec/variable_name.rb +1 -1
  88. data/lib/rubocop/cop/rspec/verified_doubles.rb +2 -0
  89. data/lib/rubocop/cop/rspec/void_expect.rb +3 -0
  90. data/lib/rubocop/cop/rspec/yield.rb +3 -0
  91. data/lib/rubocop/cop/rspec_cops.rb +0 -1
  92. data/lib/rubocop/rspec/align_let_brace.rb +1 -1
  93. data/lib/rubocop/rspec/concept.rb +2 -2
  94. data/lib/rubocop/rspec/config_formatter.rb +5 -3
  95. data/lib/rubocop/rspec/corrector/move_node.rb +7 -10
  96. data/lib/rubocop/rspec/example.rb +5 -0
  97. data/lib/rubocop/rspec/example_group.rb +15 -5
  98. data/lib/rubocop/rspec/hook.rb +2 -1
  99. data/lib/rubocop/rspec/inject.rb +4 -2
  100. data/lib/rubocop/rspec/language.rb +159 -115
  101. data/lib/rubocop/rspec/language/node_pattern.rb +7 -24
  102. data/lib/rubocop/rspec/node.rb +1 -1
  103. data/lib/rubocop/rspec/version.rb +1 -1
  104. metadata +27 -16
  105. data/lib/rubocop/cop/rspec/cop.rb +0 -10
  106. data/lib/rubocop/cop/rspec/invalid_predicate_matcher.rb +0 -41
  107. data/lib/rubocop/rspec.rb +0 -12
  108. data/lib/rubocop/rspec/empty_line_separation.rb +0 -48
  109. data/lib/rubocop/rspec/final_end_location.rb +0 -17
  110. data/lib/rubocop/rspec/top_level_describe.rb +0 -52
  111. data/lib/rubocop/rspec/top_level_group.rb +0 -57
  112. data/lib/rubocop/rspec/variable.rb +0 -16
@@ -20,25 +20,43 @@ module RuboCop
20
20
  # describe MyClass do
21
21
  # end
22
22
  class Focus < Base
23
- MSG = 'Focused spec found.'
23
+ extend AutoCorrector
24
+ include RangeHelp
24
25
 
25
- focused = ExampleGroups::FOCUSED + Examples::FOCUSED
26
+ MSG = 'Focused spec found.'
26
27
 
27
- def_node_matcher :focusable_selector?,
28
- (ExampleGroups::GROUPS + ExampleGroups::SKIPPED +
29
- Examples::EXAMPLES + Examples::SKIPPED +
30
- Examples::PENDING).node_pattern_union
28
+ # @!method focusable_selector?(node)
29
+ def_node_matcher :focusable_selector?, <<-PATTERN
30
+ {
31
+ #ExampleGroups.regular
32
+ #ExampleGroups.skipped
33
+ #Examples.regular
34
+ #Examples.skipped
35
+ #Examples.pending
36
+ }
37
+ PATTERN
31
38
 
39
+ # @!method metadata(node)
32
40
  def_node_matcher :metadata, <<-PATTERN
33
41
  {(send #rspec? #focusable_selector? <$(sym :focus) ...>)
34
42
  (send #rspec? #focusable_selector? ... (hash <$(pair (sym :focus) true) ...>))}
35
43
  PATTERN
36
44
 
37
- def_node_matcher :focused_block?, focused.send_pattern
45
+ # @!method focused_block?(node)
46
+ def_node_matcher :focused_block?,
47
+ send_pattern(<<~PATTERN)
48
+ {#ExampleGroups.focused #Examples.focused}
49
+ PATTERN
38
50
 
39
51
  def on_send(node)
40
52
  focus_metadata(node) do |focus|
41
- add_offense(focus)
53
+ add_offense(focus) do |corrector|
54
+ if focus.pair_type? || focus.str_type? || focus.sym_type?
55
+ corrector.remove(with_surrounding(focus))
56
+ elsif focus.send_type?
57
+ correct_send(corrector, focus)
58
+ end
59
+ end
42
60
  end
43
61
  end
44
62
 
@@ -49,6 +67,26 @@ module RuboCop
49
67
 
50
68
  metadata(node, &block)
51
69
  end
70
+
71
+ def with_surrounding(focus)
72
+ range_with_space = range_with_surrounding_space(
73
+ range: focus.loc.expression,
74
+ side: :left
75
+ )
76
+
77
+ range_with_surrounding_comma(range_with_space, :left)
78
+ end
79
+
80
+ def correct_send(corrector, focus)
81
+ range = focus.loc.selector
82
+ unfocused = focus.method_name.to_s.sub(/^f/, '')
83
+ unless Examples.regular(unfocused) || ExampleGroups.regular(unfocused)
84
+ return
85
+ end
86
+
87
+ corrector.replace(range,
88
+ range.source.sub(focus.method_name.to_s, unfocused))
89
+ end
52
90
  end
53
91
  end
54
92
  end
@@ -64,13 +64,13 @@ module RuboCop
64
64
  IMPLICIT_MSG = 'Omit the default `%<scope>p` argument for RSpec hooks.'
65
65
  EXPLICIT_MSG = 'Use `%<scope>p` for RSpec hooks.'
66
66
 
67
- def_node_matcher :hook?, Hooks::ALL.node_pattern_union
68
-
67
+ # @!method scoped_hook(node)
69
68
  def_node_matcher :scoped_hook, <<-PATTERN
70
- (block $(send _ #hook? (sym ${:each :example})) ...)
69
+ (block $(send _ #Hooks.all (sym ${:each :example})) ...)
71
70
  PATTERN
72
71
 
73
- def_node_matcher :unscoped_hook, '(block $(send _ #hook?) ...)'
72
+ # @!method unscoped_hook(node)
73
+ def_node_matcher :unscoped_hook, '(block $(send _ #Hooks.all) ...)'
74
74
 
75
75
  def on_block(node)
76
76
  hook(node) do |method_send, scope_name|
@@ -28,10 +28,11 @@ module RuboCop
28
28
 
29
29
  MSG = 'Move `%<hook>s` above the examples in the group.'
30
30
 
31
+ # @!method example_or_group?(node)
31
32
  def_node_matcher :example_or_group?, <<-PATTERN
32
33
  {
33
- #{(Examples::ALL + ExampleGroups::ALL).block_pattern}
34
- #{Includes::EXAMPLES.send_pattern}
34
+ #{block_pattern('{#ExampleGroups.all #Examples.all}')}
35
+ #{send_pattern('#Includes.examples')}
35
36
  }
36
37
  PATTERN
37
38
 
@@ -18,7 +18,9 @@ module RuboCop
18
18
  # end
19
19
  class ImplicitBlockExpectation < Base
20
20
  MSG = 'Avoid implicit block expectations.'
21
+ RESTRICT_ON_SEND = %i[is_expected should should_not].freeze
21
22
 
23
+ # @!method lambda?(node)
22
24
  def_node_matcher :lambda?, <<-PATTERN
23
25
  {
24
26
  (send (const nil? :Proc) :new)
@@ -26,8 +28,10 @@ module RuboCop
26
28
  }
27
29
  PATTERN
28
30
 
31
+ # @!method lambda_subject?(node)
29
32
  def_node_matcher :lambda_subject?, '(block #lambda? ...)'
30
33
 
34
+ # @!method implicit_expect(node)
31
35
  def_node_matcher :implicit_expect, <<-PATTERN
32
36
  $(send nil? {:is_expected :should :should_not} ...)
33
37
  PATTERN
@@ -30,10 +30,11 @@ module RuboCop
30
30
 
31
31
  MSG = 'Prefer `%<good>s` over `%<bad>s`.'
32
32
 
33
+ # @!method implicit_expect(node)
33
34
  def_node_matcher :implicit_expect, <<-PATTERN
34
35
  {
35
36
  (send nil? ${:should :should_not} ...)
36
- (send (send nil? $:is_expected) #{Runners::ALL.node_pattern_union} ...)
37
+ (send (send nil? $:is_expected) #Runners.all ...)
37
38
  }
38
39
  PATTERN
39
40
 
@@ -31,7 +31,9 @@ module RuboCop
31
31
  include ConfigurableEnforcedStyle
32
32
 
33
33
  MSG = "Don't use implicit subject."
34
+ RESTRICT_ON_SEND = %i[is_expected should should_not].freeze
34
35
 
36
+ # @!method implicit_subject?(node)
35
37
  def_node_matcher :implicit_subject?, <<-PATTERN
36
38
  (send nil? {:should :should_not :is_expected} ...)
37
39
  PATTERN
@@ -21,9 +21,10 @@ module RuboCop
21
21
  class InstanceSpy < Base
22
22
  extend AutoCorrector
23
23
 
24
- MSG = 'Use `instance_spy` when you check your double '\
24
+ MSG = 'Use `instance_spy` when you check your double ' \
25
25
  'with `have_received`.'
26
26
 
27
+ # @!method null_double(node)
27
28
  def_node_search :null_double, <<-PATTERN
28
29
  (lvasgn $_
29
30
  (send
@@ -31,6 +32,7 @@ module RuboCop
31
32
  ...) :as_null_object))
32
33
  PATTERN
33
34
 
35
+ # @!method have_received_usage(node)
34
36
  def_node_search :have_received_usage, <<-PATTERN
35
37
  (send
36
38
  (send nil? :expect
@@ -47,15 +47,17 @@ module RuboCop
47
47
  # end
48
48
  #
49
49
  class InstanceVariable < Base
50
- include RuboCop::RSpec::TopLevelGroup
50
+ include TopLevelGroup
51
51
 
52
52
  MSG = 'Avoid instance variables – use let, ' \
53
53
  'a method call, or a local variable (if possible).'
54
54
 
55
+ # @!method dynamic_class?(node)
55
56
  def_node_matcher :dynamic_class?, <<-PATTERN
56
57
  (block (send (const nil? :Class) :new ...) ...)
57
58
  PATTERN
58
59
 
60
+ # @!method custom_matcher?(node)
59
61
  def_node_matcher :custom_matcher?, <<-PATTERN
60
62
  (block {
61
63
  (send nil? :matcher sym)
@@ -63,8 +65,10 @@ module RuboCop
63
65
  } ...)
64
66
  PATTERN
65
67
 
68
+ # @!method ivar_usage(node)
66
69
  def_node_search :ivar_usage, '$(ivar $_)'
67
70
 
71
+ # @!method ivar_assigned?(node)
68
72
  def_node_search :ivar_assigned?, '(ivasgn % ...)'
69
73
 
70
74
  def on_top_level_group(node)
@@ -22,9 +22,11 @@ module RuboCop
22
22
  extend AutoCorrector
23
23
  include ConfigurableEnforcedStyle
24
24
 
25
- MSG = 'Prefer `%<replacement>s` over `%<original>s` when including '\
25
+ MSG = 'Prefer `%<replacement>s` over `%<original>s` when including ' \
26
26
  'examples in a nested context.'
27
+ RESTRICT_ON_SEND = %i[it_behaves_like it_should_behave_like].freeze
27
28
 
29
+ # @!method example_inclusion_offense(node)
28
30
  def_node_matcher :example_inclusion_offense, '(send _ % ...)'
29
31
 
30
32
  def on_send(node)
@@ -17,8 +17,9 @@ module RuboCop
17
17
  # end
18
18
  class IteratedExpectation < Base
19
19
  MSG = 'Prefer using the `all` matcher instead ' \
20
- 'of iterating over an array.'
20
+ 'of iterating over an array.'
21
21
 
22
+ # @!method each?(node)
22
23
  def_node_matcher :each?, <<-PATTERN
23
24
  (block
24
25
  (send ... :each)
@@ -27,6 +28,7 @@ module RuboCop
27
28
  )
28
29
  PATTERN
29
30
 
31
+ # @!method expectation?(node)
30
32
  def_node_matcher :expectation?, <<-PATTERN
31
33
  (send (send nil? :expect (lvar %)) :to ...)
32
34
  PATTERN
@@ -35,10 +35,11 @@ module RuboCop
35
35
 
36
36
  MSG = 'Move `let` before the examples in the group.'
37
37
 
38
+ # @!method example_or_group?(node)
38
39
  def_node_matcher :example_or_group?, <<-PATTERN
39
40
  {
40
- #{(Examples::ALL + ExampleGroups::ALL).block_pattern}
41
- #{Includes::EXAMPLES.send_pattern}
41
+ #{block_pattern('{#ExampleGroups.all #Examples.all}')}
42
+ #{send_pattern('#Includes.examples')}
42
43
  }
43
44
  PATTERN
44
45
 
@@ -28,12 +28,17 @@ module RuboCop
28
28
  class LetSetup < Base
29
29
  MSG = 'Do not use `let!` to setup objects not referenced in tests.'
30
30
 
31
+ # @!method example_or_shared_group_or_including?(node)
31
32
  def_node_matcher :example_or_shared_group_or_including?,
32
- (
33
- ExampleGroups::ALL + SharedGroups::ALL +
34
- Includes::ALL
35
- ).block_pattern
33
+ block_pattern(<<~PATTERN)
34
+ {
35
+ #SharedGroups.all
36
+ #ExampleGroups.all
37
+ #Includes.all
38
+ }
39
+ PATTERN
36
40
 
41
+ # @!method let_bang(node)
37
42
  def_node_matcher :let_bang, <<-PATTERN
38
43
  {
39
44
  (block $(send nil? :let! {(sym $_) (str $_)}) ...)
@@ -41,6 +46,7 @@ module RuboCop
41
46
  }
42
47
  PATTERN
43
48
 
49
+ # @!method method_called?(node)
44
50
  def_node_search :method_called?, '(send nil? %)'
45
51
 
46
52
  def on_block(node)
@@ -15,18 +15,12 @@ module RuboCop
15
15
  #
16
16
  class MessageChain < Base
17
17
  MSG = 'Avoid stubbing using `%<method>s`.'
18
-
19
- def_node_matcher :message_chain, <<-PATTERN
20
- (send _ {:receive_message_chain :stub_chain} ...)
21
- PATTERN
18
+ RESTRICT_ON_SEND = %i[receive_message_chain stub_chain].freeze
22
19
 
23
20
  def on_send(node)
24
- message_chain(node) do
25
- add_offense(
26
- node.loc.selector,
27
- message: format(MSG, method: node.method_name)
28
- )
29
- end
21
+ add_offense(
22
+ node.loc.selector, message: format(MSG, method: node.method_name)
23
+ )
30
24
  end
31
25
  end
32
26
  end
@@ -30,11 +30,14 @@ module RuboCop
30
30
  MSG = 'Prefer `%<style>s` for setting message expectations.'
31
31
 
32
32
  SUPPORTED_STYLES = %w[allow expect].freeze
33
+ RESTRICT_ON_SEND = %i[to].freeze
33
34
 
35
+ # @!method message_expectation(node)
34
36
  def_node_matcher :message_expectation, <<-PATTERN
35
37
  (send $(send nil? {:expect :allow} ...) :to #receive_message?)
36
38
  PATTERN
37
39
 
40
+ # @!method receive_message?(node)
38
41
  def_node_search :receive_message?, '(send nil? :receive ...)'
39
42
 
40
43
  def on_send(node)
@@ -29,16 +29,18 @@ module RuboCop
29
29
 
30
30
  MSG_RECEIVE = 'Prefer `receive` for setting message expectations.'
31
31
 
32
- MSG_HAVE_RECEIVED = 'Prefer `have_received` for setting message '\
33
- 'expectations. Setup `%<source>s` as a spy using '\
32
+ MSG_HAVE_RECEIVED = 'Prefer `have_received` for setting message ' \
33
+ 'expectations. Setup `%<source>s` as a spy using ' \
34
34
  '`allow` or `instance_spy`.'
35
35
 
36
36
  SUPPORTED_STYLES = %w[have_received receive].freeze
37
37
 
38
+ # @!method message_expectation(node)
38
39
  def_node_matcher :message_expectation, %(
39
- (send (send nil? :expect $_) #{Runners::ALL.node_pattern_union} ...)
40
+ (send (send nil? :expect $_) #Runners.all ...)
40
41
  )
41
42
 
43
+ # @!method receive_message(node)
42
44
  def_node_search :receive_message, %(
43
45
  $(send nil? {:receive :have_received} ...)
44
46
  )
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module RSpec
6
+ # Help methods for working with nodes containing comments.
7
+ module CommentsHelp
8
+ include FinalEndLocation
9
+
10
+ def source_range_with_comment(node)
11
+ begin_pos = begin_pos_with_comment(node).begin_pos
12
+ end_pos = end_line_position(node).end_pos
13
+
14
+ Parser::Source::Range.new(buffer, begin_pos, end_pos)
15
+ end
16
+
17
+ def begin_pos_with_comment(node)
18
+ first_comment = processed_source.ast_with_comments[node].first
19
+
20
+ start_line_position(first_comment || node)
21
+ end
22
+
23
+ def start_line_position(node)
24
+ buffer.line_range(node.loc.line)
25
+ end
26
+
27
+ def end_line_position(node)
28
+ end_line = buffer.line_for_position(final_end_location(node).end_pos)
29
+ buffer.line_range(end_line)
30
+ end
31
+
32
+ def buffer
33
+ processed_source.buffer
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module RSpec
6
+ # Helps determine the offending location if there is not an empty line
7
+ # following the node. Allows comments to follow directly after.
8
+ module EmptyLineSeparation
9
+ include FinalEndLocation
10
+ include RangeHelp
11
+
12
+ def missing_separating_line_offense(node)
13
+ return if last_child?(node)
14
+
15
+ missing_separating_line(node) do |location|
16
+ msg = yield(node.method_name)
17
+ add_offense(location, message: msg) do |corrector|
18
+ corrector.insert_after(location.end, "\n")
19
+ end
20
+ end
21
+ end
22
+
23
+ def missing_separating_line(node)
24
+ line = final_end_location(node).line
25
+
26
+ line += 1 while comment_line?(processed_source[line])
27
+
28
+ return if processed_source[line].blank?
29
+
30
+ yield offending_loc(line)
31
+ end
32
+
33
+ def offending_loc(last_line)
34
+ offending_line = processed_source[last_line - 1]
35
+
36
+ content_length = offending_line.lstrip.length
37
+ start = offending_line.length - content_length
38
+
39
+ source_range(processed_source.buffer,
40
+ last_line, start, content_length)
41
+ end
42
+
43
+ def last_child?(node)
44
+ return true unless node.parent&.begin_type?
45
+
46
+ node.equal?(node.parent.children.last)
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module RSpec
6
+ # Helps find the true end location of nodes which might contain heredocs.
7
+ module FinalEndLocation
8
+ def final_end_location(start_node)
9
+ heredoc_endings =
10
+ start_node.each_node(:str, :dstr, :xstr)
11
+ .select(&:heredoc?)
12
+ .map { |node| node.loc.heredoc_end }
13
+
14
+ [start_node.loc.end, *heredoc_endings].max_by(&:line)
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end