rubocop-rspec 2.0.0.pre → 2.3.0

Sign up to get free protection for your applications and to get access to all the features.
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