gitlab-styles 7.1.0 → 8.0.0

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