gitlab-styles 6.6.0 → 8.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (68) hide show
  1. checksums.yaml +4 -4
  2. data/.gitlab/changelog_config.yml +13 -0
  3. data/.gitlab/merge_request_templates/Release.md +9 -31
  4. data/.gitlab-ci.yml +3 -1
  5. data/Dangerfile +5 -0
  6. data/gitlab-styles.gemspec +1 -0
  7. data/lib/gitlab/styles/common/banned_constants.rb +28 -0
  8. data/lib/gitlab/styles/rubocop/model_helpers.rb +1 -1
  9. data/lib/gitlab/styles/rubocop.rb +2 -2
  10. data/lib/gitlab/styles/version.rb +1 -1
  11. data/lib/rubocop/cop/active_record_dependent.rb +32 -0
  12. data/lib/rubocop/cop/active_record_serialize.rb +20 -0
  13. data/lib/rubocop/cop/avoid_return_from_blocks.rb +77 -0
  14. data/lib/rubocop/cop/code_reuse/active_record.rb +80 -0
  15. data/lib/rubocop/cop/custom_error_class.rb +69 -0
  16. data/lib/rubocop/cop/fips/md5.rb +27 -0
  17. data/lib/rubocop/cop/fips/open_ssl.rb +31 -0
  18. data/lib/rubocop/cop/fips/sha1.rb +27 -0
  19. data/lib/rubocop/cop/gem_fetcher.rb +37 -0
  20. data/lib/rubocop/cop/in_batches.rb +18 -0
  21. data/lib/rubocop/cop/internal_affairs/deprecate_cop_helper.rb +39 -0
  22. data/lib/rubocop/cop/line_break_after_guard_clauses.rb +100 -0
  23. data/lib/rubocop/cop/line_break_around_conditional_block.rb +128 -0
  24. data/lib/rubocop/cop/migration/update_large_table.rb +60 -0
  25. data/lib/rubocop/cop/performance/rubyzip.rb +35 -0
  26. data/lib/rubocop/cop/polymorphic_associations.rb +25 -0
  27. data/lib/rubocop/cop/rails/include_url_helper.rb +27 -0
  28. data/lib/rubocop/cop/redirect_with_status.rb +46 -0
  29. data/lib/rubocop/cop/rspec/base.rb +14 -0
  30. data/lib/rubocop/cop/rspec/empty_line_after_final_let_it_be.rb +47 -0
  31. data/lib/rubocop/cop/rspec/empty_line_after_let_block.rb +61 -0
  32. data/lib/rubocop/cop/rspec/empty_line_after_shared_example.rb +61 -0
  33. data/lib/rubocop/cop/rspec/example_starting_character.rb +120 -0
  34. data/lib/rubocop/cop/rspec/have_link_parameters.rb +44 -0
  35. data/lib/rubocop/cop/rspec/single_line_hook.rb +41 -0
  36. data/lib/rubocop/cop/rspec/verbose_include_metadata.rb +71 -0
  37. data/lib/rubocop/cop/style/hash_transformation.rb +83 -0
  38. data/lib/rubocop/cop/style/open_struct_use.rb +39 -0
  39. data/lib/rubocop/cop/without_reactive_cache.rb +16 -0
  40. data/rubocop-default.yml +1 -0
  41. data/rubocop-fips.yml +15 -0
  42. data/rubocop-rspec.yml +3 -2
  43. metadata +49 -27
  44. data/lib/gitlab/styles/rubocop/cop/active_record_dependent.rb +0 -32
  45. data/lib/gitlab/styles/rubocop/cop/active_record_serialize.rb +0 -24
  46. data/lib/gitlab/styles/rubocop/cop/code_reuse/active_record.rb +0 -130
  47. data/lib/gitlab/styles/rubocop/cop/custom_error_class.rb +0 -73
  48. data/lib/gitlab/styles/rubocop/cop/gem_fetcher.rb +0 -41
  49. data/lib/gitlab/styles/rubocop/cop/in_batches.rb +0 -22
  50. data/lib/gitlab/styles/rubocop/cop/internal_affairs/deprecate_cop_helper.rb +0 -43
  51. data/lib/gitlab/styles/rubocop/cop/line_break_after_guard_clauses.rb +0 -104
  52. data/lib/gitlab/styles/rubocop/cop/line_break_around_conditional_block.rb +0 -132
  53. data/lib/gitlab/styles/rubocop/cop/migration/update_large_table.rb +0 -64
  54. data/lib/gitlab/styles/rubocop/cop/performance/rubyzip.rb +0 -39
  55. data/lib/gitlab/styles/rubocop/cop/polymorphic_associations.rb +0 -29
  56. data/lib/gitlab/styles/rubocop/cop/rails/include_url_helper.rb +0 -31
  57. data/lib/gitlab/styles/rubocop/cop/redirect_with_status.rb +0 -50
  58. data/lib/gitlab/styles/rubocop/cop/rspec/base.rb +0 -18
  59. data/lib/gitlab/styles/rubocop/cop/rspec/empty_line_after_final_let_it_be.rb +0 -51
  60. data/lib/gitlab/styles/rubocop/cop/rspec/empty_line_after_let_block.rb +0 -65
  61. data/lib/gitlab/styles/rubocop/cop/rspec/empty_line_after_shared_example.rb +0 -65
  62. data/lib/gitlab/styles/rubocop/cop/rspec/example_starting_character.rb +0 -124
  63. data/lib/gitlab/styles/rubocop/cop/rspec/have_link_parameters.rb +0 -48
  64. data/lib/gitlab/styles/rubocop/cop/rspec/single_line_hook.rb +0 -45
  65. data/lib/gitlab/styles/rubocop/cop/rspec/verbose_include_metadata.rb +0 -75
  66. data/lib/gitlab/styles/rubocop/cop/style/hash_transformation.rb +0 -87
  67. data/lib/gitlab/styles/rubocop/cop/style/open_struct_use.rb +0 -43
  68. data/lib/gitlab/styles/rubocop/cop/without_reactive_cache.rb +0 -20
@@ -0,0 +1,100 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rubocop
4
+ module Cop
5
+ # Ensures a line break after guard clauses.
6
+ #
7
+ # @example
8
+ # # bad
9
+ # return unless condition
10
+ # do_stuff
11
+ #
12
+ # # good
13
+ # return unless condition
14
+ #
15
+ # do_stuff
16
+ #
17
+ # # bad
18
+ # raise if condition
19
+ # do_stuff
20
+ #
21
+ # # good
22
+ # raise if condition
23
+ #
24
+ # do_stuff
25
+ #
26
+ # Multiple guard clauses are allowed without
27
+ # line break.
28
+ #
29
+ # # good
30
+ # return unless condition_a
31
+ # return unless condition_b
32
+ #
33
+ # do_stuff
34
+ #
35
+ # Guard clauses in case statement are allowed without
36
+ # line break.
37
+ #
38
+ # # good
39
+ # case model
40
+ # when condition_a
41
+ # return true unless condition_b
42
+ # when
43
+ # ...
44
+ # end
45
+ #
46
+ # Guard clauses before end are allowed without
47
+ # line break.
48
+ #
49
+ # # good
50
+ # if condition_a
51
+ # do_something
52
+ # else
53
+ # do_something_else
54
+ # return unless condition
55
+ # end
56
+ #
57
+ # do_something_more
58
+ class LineBreakAfterGuardClauses < RuboCop::Cop::Cop
59
+ MSG = 'Add a line break after guard clauses'
60
+
61
+ def_node_matcher :guard_clause_node?, <<-PATTERN
62
+ [{(send nil? {:raise :fail :throw} ...) return break next} single_line?]
63
+ PATTERN
64
+
65
+ def on_if(node)
66
+ return unless node.single_line?
67
+ return unless guard_clause?(node)
68
+ return if next_line(node).blank? || clause_last_line?(next_line(node)) || guard_clause?(next_sibling(node))
69
+
70
+ add_offense(node)
71
+ end
72
+
73
+ def autocorrect(node)
74
+ lambda do |corrector|
75
+ corrector.insert_after(node.loc.expression, "\n")
76
+ end
77
+ end
78
+
79
+ private
80
+
81
+ def guard_clause?(node)
82
+ return false unless node.if_type?
83
+
84
+ guard_clause_node?(node.if_branch)
85
+ end
86
+
87
+ def next_sibling(node)
88
+ node.parent.children[node.sibling_index + 1]
89
+ end
90
+
91
+ def next_line(node)
92
+ processed_source[node.loc.line]
93
+ end
94
+
95
+ def clause_last_line?(line)
96
+ line =~ /^\s*(?:end|elsif|else|when|rescue|ensure)/
97
+ end
98
+ end
99
+ end
100
+ end
@@ -0,0 +1,128 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rubocop
4
+ module Cop
5
+ # Ensures a line break around conditional blocks.
6
+ #
7
+ # @example
8
+ # # bad
9
+ # do_something
10
+ # if condition
11
+ # do_extra_stuff
12
+ # end
13
+ # do_something_more
14
+ #
15
+ # # good
16
+ # do_something
17
+ #
18
+ # if condition
19
+ # do_extra_stuff
20
+ # end
21
+ #
22
+ # do_something_more
23
+ #
24
+ # # bad
25
+ # do_something
26
+ # unless condition
27
+ # do_extra_stuff
28
+ # end
29
+ #
30
+ # do_something_more
31
+ #
32
+ # # good
33
+ # def a_method
34
+ # if condition
35
+ # do_something
36
+ # end
37
+ # end
38
+ #
39
+ # # good
40
+ # on_block do
41
+ # if condition
42
+ # do_something
43
+ # end
44
+ # end
45
+ class LineBreakAroundConditionalBlock < RuboCop::Cop::Cop
46
+ include RuboCop::Cop::RangeHelp
47
+
48
+ MSG = 'Add a line break around conditional blocks'
49
+
50
+ def on_if(node)
51
+ # This cop causes errors in haml files, so let's skip those
52
+ return if in_haml?(node)
53
+ return if node.single_line?
54
+ return unless node.if? || node.unless?
55
+
56
+ add_offense(node) unless previous_line_valid?(node)
57
+ add_offense(node) unless last_line_valid?(node)
58
+ end
59
+
60
+ def autocorrect(node)
61
+ lambda do |corrector|
62
+ line = range_by_whole_lines(node.source_range)
63
+
64
+ corrector.insert_before(line, "\n") unless previous_line_valid?(node)
65
+ corrector.insert_after(line, "\n") unless last_line_valid?(node)
66
+ end
67
+ end
68
+
69
+ private
70
+
71
+ def previous_line_valid?(node)
72
+ previous_line(node).empty? ||
73
+ start_clause_line?(previous_line(node)) ||
74
+ block_start?(previous_line(node)) ||
75
+ begin_line?(previous_line(node)) ||
76
+ assignment_line?(previous_line(node)) ||
77
+ rescue_line?(previous_line(node))
78
+ end
79
+
80
+ def last_line_valid?(node)
81
+ last_line(node).empty? ||
82
+ end_line?(last_line(node)) ||
83
+ end_clause_line?(last_line(node))
84
+ end
85
+
86
+ def previous_line(node)
87
+ processed_source[node.loc.line - 2]
88
+ end
89
+
90
+ def last_line(node)
91
+ processed_source[node.loc.last_line]
92
+ end
93
+
94
+ def start_clause_line?(line)
95
+ line =~ /^\s*(def|=end|#|module|class|if|unless|else|elsif|ensure|when)/
96
+ end
97
+
98
+ def end_clause_line?(line)
99
+ line =~ /^\s*(#|rescue|else|elsif|when)/
100
+ end
101
+
102
+ def begin_line?(line)
103
+ # an assignment followed by a begin or ust a begin
104
+ line =~ /^\s*(@?(\w|\|+|=|\[|\]|\s)+begin|begin)/
105
+ end
106
+
107
+ def assignment_line?(line)
108
+ line =~ /^\s*.*=/
109
+ end
110
+
111
+ def rescue_line?(line)
112
+ line =~ /^\s*rescue/
113
+ end
114
+
115
+ def block_start?(line)
116
+ line.match(/ (do|{)( \|.*?\|)?\s?(#.+)?\z/)
117
+ end
118
+
119
+ def end_line?(line)
120
+ line =~ /^\s*(end|})/
121
+ end
122
+
123
+ def in_haml?(node)
124
+ node.location.expression.source_buffer.name.end_with?('.haml.rb')
125
+ end
126
+ end
127
+ end
128
+ end
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+ require_relative '../../../gitlab/styles/rubocop/migration_helpers'
3
+
4
+ module Rubocop
5
+ module Cop
6
+ module Migration
7
+ # This cop checks for methods that may lead to batch type issues on a table that's been
8
+ # explicitly denied because of its size.
9
+ #
10
+ # Even though though these methods perform functions to avoid
11
+ # downtime, using it with tables with millions of rows still causes a
12
+ # significant delay in the deploy process and is best avoided.
13
+ #
14
+ # See https://gitlab.com/gitlab-com/infrastructure/issues/1602 for more
15
+ # information.
16
+ class UpdateLargeTable < RuboCop::Cop::Cop
17
+ include Gitlab::Styles::Rubocop::MigrationHelpers
18
+
19
+ MSG = 'Using `%s` on the `%s` table will take a long time to ' \
20
+ 'complete, and should be avoided unless absolutely ' \
21
+ 'necessary'
22
+
23
+ def_node_matcher :batch_update?, <<~PATTERN
24
+ (send nil? ${#denied_method?}
25
+ (sym $...)
26
+ ...)
27
+ PATTERN
28
+
29
+ def on_send(node)
30
+ return if denied_tables.empty? || denied_methods.empty?
31
+ return unless in_migration?(node)
32
+
33
+ matches = batch_update?(node)
34
+ return unless matches
35
+
36
+ update_method = matches.first
37
+ table = matches.last.to_a.first
38
+
39
+ return unless denied_tables.include?(table)
40
+
41
+ add_offense(node, message: format(MSG, update_method, table))
42
+ end
43
+
44
+ private
45
+
46
+ def denied_tables
47
+ cop_config['DeniedTables'] || []
48
+ end
49
+
50
+ def denied_method?(method_name)
51
+ denied_methods.include?(method_name)
52
+ end
53
+
54
+ def denied_methods
55
+ cop_config['DeniedMethods'] || []
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rubocop
4
+ module Cop
5
+ module Performance
6
+ # This cop flags inefficient uses of rubyzip's Zip::File, since when instantiated
7
+ # it reads the file's Central Directory into memory entirely. For zips with many
8
+ # files and directories, this can be very expensive even when the archive's size
9
+ # in bytes is small.
10
+ #
11
+ # See also:
12
+ # - https://github.com/rubyzip/rubyzip/issues/506
13
+ # - https://github.com/rubyzip/rubyzip#notes-on-zipinputstream
14
+ class Rubyzip < RuboCop::Cop::Cop
15
+ MSG = 'Be careful when opening or iterating zip files via Zip::File. ' \
16
+ 'Zip archives may contain many entries, and their file index is ' \
17
+ 'read into memory upon construction, which can lead to ' \
18
+ 'high memory use and poor performance. ' \
19
+ 'Consider iterating archive entries via Zip::InputStream instead.'
20
+
21
+ def_node_matcher :reads_central_directory?, <<-PATTERN
22
+ (send
23
+ (const
24
+ (const {nil? (cbase)} :Zip) :File) {:new :open :foreach} ...)
25
+ PATTERN
26
+
27
+ def on_send(node)
28
+ return unless reads_central_directory?(node)
29
+
30
+ add_offense(node)
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../../gitlab/styles/rubocop/model_helpers'
4
+
5
+ module Rubocop
6
+ module Cop
7
+ # Cop that prevents the use of polymorphic associations
8
+ class PolymorphicAssociations < RuboCop::Cop::Cop
9
+ include Gitlab::Styles::Rubocop::ModelHelpers
10
+
11
+ MSG = 'Do not use polymorphic associations, use separate tables instead'
12
+
13
+ def on_send(node)
14
+ return unless in_model?(node)
15
+ return unless node.children[1] == :belongs_to
16
+
17
+ node.children.last.each_node(:pair) do |pair|
18
+ key_name = pair.children[0].children[0]
19
+
20
+ add_offense(pair) if key_name == :polymorphic
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../../../gitlab/styles/rubocop/model_helpers'
4
+
5
+ module Rubocop
6
+ module Cop
7
+ module Rails
8
+ class IncludeUrlHelper < RuboCop::Cop::Cop
9
+ MSG = <<~MSG
10
+ Avoid including `ActionView::Helpers::UrlHelper`.
11
+ It adds/overrides ~40 methods while usually only one is needed.
12
+ Instead, use the `Gitlab::Routing.url_helpers`/`Application.routes.url_helpers`(outside of gitlab)
13
+ and `ActionController::Base.helpers.link_to`.
14
+ See https://gitlab.com/gitlab-org/gitlab/-/issues/340567.
15
+ MSG
16
+
17
+ def_node_matcher :include_url_helpers_node?, <<~PATTERN
18
+ (send nil? :include (const (const (const {nil? cbase} :ActionView) :Helpers) :UrlHelper))
19
+ PATTERN
20
+
21
+ def on_send(node)
22
+ add_offense(node) if include_url_helpers_node?(node)
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rubocop
4
+ module Cop
5
+ # This cop prevents usage of 'redirect_to' in actions 'destroy' without specifying 'status'.
6
+ # See https://gitlab.com/gitlab-org/gitlab-ce/issues/31840
7
+ class RedirectWithStatus < RuboCop::Cop::Cop
8
+ MSG = 'Do not use "redirect_to" without "status" in "destroy" action'
9
+
10
+ def on_def(node)
11
+ return unless in_controller?(node)
12
+ return unless destroy?(node) || destroy_all?(node)
13
+
14
+ node.each_descendant(:send) do |def_node|
15
+ next unless redirect_to?(def_node)
16
+
17
+ methods = []
18
+
19
+ def_node.children.last.each_node(:pair) do |pair|
20
+ methods << pair.children.first.children.first
21
+ end
22
+
23
+ add_offense(def_node, location: :selector) unless methods.include?(:status)
24
+ end
25
+ end
26
+
27
+ private
28
+
29
+ def in_controller?(node)
30
+ node.location.expression.source_buffer.name.end_with?('_controller.rb')
31
+ end
32
+
33
+ def destroy?(node)
34
+ node.children.first == :destroy
35
+ end
36
+
37
+ def destroy_all?(node)
38
+ node.children.first == :destroy_all
39
+ end
40
+
41
+ def redirect_to?(node)
42
+ node.children[1] == :redirect_to
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rubocop-rspec'
4
+ require_relative '../../../gitlab/styles/rubocop/rspec/helpers'
5
+
6
+ module Rubocop
7
+ module Cop
8
+ module RSpec
9
+ class Base < RuboCop::Cop::RSpec::Base
10
+ include Gitlab::Styles::Rubocop::Rspec::Helpers
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rubocop-rspec'
4
+ require_relative 'base'
5
+
6
+ module Rubocop
7
+ module Cop
8
+ module RSpec
9
+ # Checks if there is an empty line after the last `let_it_be` block.
10
+ #
11
+ # @example
12
+ # # bad
13
+ # let_it_be(:foo) { bar }
14
+ # let_it_be(:something) { other }
15
+ # it { does_something }
16
+ #
17
+ # # good
18
+ # let_it_be(:foo) { bar }
19
+ # let_it_be(:something) { other }
20
+ #
21
+ # it { does_something }
22
+ class EmptyLineAfterFinalLetItBe < Base
23
+ extend RuboCop::Cop::AutoCorrector
24
+ include RuboCop::RSpec::EmptyLineSeparation
25
+
26
+ MSG = 'Add an empty line after the last `let_it_be`.'
27
+
28
+ def_node_matcher :let_it_be?, <<-PATTERN
29
+ {
30
+ (block (send #rspec? {:let_it_be :let_it_be_with_refind :let_it_be_with_reload} ...) ...)
31
+ (send #rspec? {:let_it_be :let_it_be_with_refind :let_it_be_with_reload} _ block_pass)
32
+ }
33
+ PATTERN
34
+
35
+ def on_block(node)
36
+ return unless example_group_with_body?(node)
37
+
38
+ final_let_it_be = node.body.child_nodes.reverse.find { |child| let_it_be?(child) }
39
+
40
+ return if final_let_it_be.nil?
41
+
42
+ missing_separating_line_offense(final_let_it_be) { MSG }
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rubocop-rspec'
4
+ require_relative 'base'
5
+
6
+ module Rubocop
7
+ module Cop
8
+ module RSpec
9
+ # Checks if there is an empty line after let blocks.
10
+ #
11
+ # @example
12
+ # # bad
13
+ # RSpec.describe Foo do
14
+ # let(:something) { 'something' }
15
+ # let(:another_thing) do
16
+ # end
17
+ # let(:something_else) do
18
+ # end
19
+ # let(:last_thing) { 'last thing' }
20
+ # end
21
+ #
22
+ # # good
23
+ # RSpec.describe Foo do
24
+ # let(:something) { 'something' }
25
+ # let(:another_thing) do
26
+ # end
27
+ #
28
+ # let(:something_else) do
29
+ # end
30
+ #
31
+ # let(:last_thing) { 'last thing' }
32
+ # end
33
+ #
34
+ # # good - it's ok to have non-separated without do/end blocks
35
+ # RSpec.describe Foo do
36
+ # let(:something) { 'something' }
37
+ # let(:last_thing) { 'last thing' }
38
+ # end
39
+ #
40
+ class EmptyLineAfterLetBlock < Base
41
+ extend RuboCop::Cop::AutoCorrector
42
+ include RuboCop::RSpec::EmptyLineSeparation
43
+
44
+ MSG = 'Add an empty line after `%<let>s` block.'
45
+
46
+ def_node_matcher :lets, LET.block_pattern
47
+
48
+ def on_block(node)
49
+ lets(node) do
50
+ break if last_child?(node)
51
+ next if node.single_line?
52
+
53
+ missing_separating_line_offense(node) do |method|
54
+ format(MSG, let: method)
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rubocop-rspec'
4
+ require_relative 'base'
5
+
6
+ module Rubocop
7
+ module Cop
8
+ module RSpec
9
+ # Checks if there is an empty line after shared example blocks.
10
+ #
11
+ # @example
12
+ # # bad
13
+ # RSpec.describe Foo do
14
+ # it_behaves_like 'do this first'
15
+ # it_behaves_like 'does this' do
16
+ # end
17
+ # it_behaves_like 'does that' do
18
+ # end
19
+ # it_behaves_like 'do some more'
20
+ # end
21
+ #
22
+ # # good
23
+ # RSpec.describe Foo do
24
+ # it_behaves_like 'do this first'
25
+ # it_behaves_like 'does this' do
26
+ # end
27
+ #
28
+ # it_behaves_like 'does that' do
29
+ # end
30
+ #
31
+ # it_behaves_like 'do some more'
32
+ # end
33
+ #
34
+ # # fair - it's ok to have non-separated without blocks
35
+ # RSpec.describe Foo do
36
+ # it_behaves_like 'do this first'
37
+ # it_behaves_like 'does this'
38
+ # end
39
+ #
40
+ class EmptyLineAfterSharedExample < Base
41
+ extend RuboCop::Cop::AutoCorrector
42
+ include RuboCop::RSpec::EmptyLineSeparation
43
+
44
+ MSG = 'Add an empty line after `%<example>s` block.'
45
+
46
+ def_node_matcher :shared_examples,
47
+ (SharedGroups::ALL + Includes::ALL).block_pattern
48
+
49
+ def on_block(node)
50
+ shared_examples(node) do
51
+ break if last_child?(node)
52
+
53
+ missing_separating_line_offense(node) do |method|
54
+ format(MSG, example: method)
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end