gitlab-styles 7.1.0 → 8.0.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 (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
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: bdbe53a3630aedb6f9a4eb87e46ab9becc5b924dbaf2122eca8f8634ffa46d11
4
- data.tar.gz: 8b64b09817b3d57edec615305e973f9aa86312bfc19b25cf999111f721d16cf3
3
+ metadata.gz: 6b56e4d8be26ddd4c64b078c7ba35ee9cde92ee9aa8510303fc8e56f55d76b4c
4
+ data.tar.gz: 9e3aae79876619f3f189ad51133ad0e5b6c9f2b50a9d08fcaa1d37275bfe4ccc
5
5
  SHA512:
6
- metadata.gz: 691d29df5dd389a90f9169e0caa3637c5abad270eeab8e55742b2d45542056d819c18f24723b1e897e1f1cc343d1a6aa740efcb17df1eb6b1517ef580ad2dabd
7
- data.tar.gz: e1ceae95be87aec10561836e415f7d12d3e59404d24604097efae1fb99290038eb77d1013f03982fae00d5ad1c819d72385a505b247580b2d16c32c7c390e7c2
6
+ metadata.gz: 7bf2ad47042480c6e187b5643c963e8e2560c8f7d38ed9431308902b0074aca0a52f740790fc2cd82e59611e50156b7dc18d89a562b17c1c8cce2c1db3ebe50b
7
+ data.tar.gz: b97431178490c4344cf1eaec23c1bf2596b7e44a2a8182ea2295add370e7e0c67da6bfa5fc4b213540306a681534e62272736669549e2949168b09bc4b1ddf8d
@@ -3,7 +3,7 @@
3
3
  module Gitlab
4
4
  module Styles
5
5
  module Rubocop
6
- module ModelHelpers
6
+ module Gitlab::Styles::Rubocop::ModelHelpers
7
7
  # Returns true if the given node originated from the models directory.
8
8
  def in_model?(node)
9
9
  path = node.location.expression.source_buffer.name
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # Auto-require all cops under `gitlab/styles/rubocop/cop/**/*.rb`
4
- cops_glob = File.join(__dir__, 'rubocop', 'cop', '**', '*.rb')
3
+ # Auto-require all cops under `rubocop/cop/**/*.rb`
4
+ cops_glob = File.join(__dir__, '..', '..', 'rubocop', 'cop', '**', '*.rb')
5
5
  Dir[cops_glob].sort.each { |cop| require(cop) }
6
6
 
7
7
  module Gitlab
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Gitlab
4
4
  module Styles
5
- VERSION = '7.1.0'
5
+ VERSION = '8.0.0'
6
6
  end
7
7
  end
@@ -0,0 +1,32 @@
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 `dependent: ...` in ActiveRecord models.
8
+ class ActiveRecordDependent < RuboCop::Cop::Cop
9
+ include Gitlab::Styles::Rubocop::ModelHelpers
10
+
11
+ MSG = 'Do not use `dependent:` to remove associated data, ' \
12
+ 'use foreign keys with cascading deletes instead.'
13
+
14
+ METHOD_NAMES = [:has_many, :has_one, :belongs_to].freeze
15
+ ALLOWED_OPTIONS = [:restrict_with_error].freeze
16
+
17
+ def on_send(node)
18
+ return unless in_model?(node)
19
+ return unless METHOD_NAMES.include?(node.children[1])
20
+
21
+ node.children.last.each_node(:pair) do |pair|
22
+ key_name = pair.children[0].children[0]
23
+ option_name = pair.children[1].children[0]
24
+
25
+ break if ALLOWED_OPTIONS.include?(option_name)
26
+
27
+ add_offense(pair) if key_name == :dependent
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,20 @@
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 `serialize` in ActiveRecord models.
8
+ class ActiveRecordSerialize < RuboCop::Cop::Cop
9
+ include Gitlab::Styles::Rubocop::ModelHelpers
10
+
11
+ MSG = 'Do not store serialized data in the database, use separate columns and/or tables instead'
12
+
13
+ def on_send(node)
14
+ return unless in_model?(node)
15
+
16
+ add_offense(node, location: :selector) if node.children[1] == :serialize
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,77 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rubocop
4
+ module Cop
5
+ # Checks for return inside blocks.
6
+ # For more information see: https://gitlab.com/gitlab-org/gitlab-foss/issues/42889
7
+ #
8
+ # @example
9
+ # # bad
10
+ # call do
11
+ # return if something
12
+ #
13
+ # do_something_else
14
+ # end
15
+ #
16
+ # # good
17
+ # call do
18
+ # break if something
19
+ #
20
+ # do_something_else
21
+ # end
22
+ #
23
+ class AvoidReturnFromBlocks < RuboCop::Cop::Cop
24
+ MSG = 'Do not return from a block, use next or break instead.'
25
+ DEF_METHODS = %i[define_method lambda].freeze
26
+ WHITELISTED_METHODS = %i[each each_filename times loop].freeze
27
+
28
+ def on_block(node)
29
+ block_body = node.body
30
+
31
+ return unless block_body
32
+ return unless top_block?(node)
33
+
34
+ block_body.each_node(:return) do |return_node|
35
+ next if parent_blocks(node, return_node).all? { |block| whitelisted?(block) }
36
+
37
+ add_offense(return_node)
38
+ end
39
+ end
40
+
41
+ private
42
+
43
+ def top_block?(node)
44
+ current_node = node
45
+ top_block = nil
46
+
47
+ while current_node && current_node.type != :def
48
+ top_block = current_node if current_node.block_type?
49
+ current_node = current_node.parent
50
+ end
51
+
52
+ top_block == node
53
+ end
54
+
55
+ def parent_blocks(node, current_node)
56
+ blocks = []
57
+
58
+ until node == current_node || def?(current_node)
59
+ blocks << current_node if current_node.block_type?
60
+ current_node = current_node.parent
61
+ end
62
+
63
+ blocks << node if node == current_node && !def?(node)
64
+ blocks
65
+ end
66
+
67
+ def def?(node)
68
+ node.def_type? || node.defs_type? ||
69
+ (node.block_type? && DEF_METHODS.include?(node.method_name))
70
+ end
71
+
72
+ def whitelisted?(block_node)
73
+ WHITELISTED_METHODS.include?(block_node.method_name)
74
+ end
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,80 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rubocop
4
+ module Cop
5
+ module CodeReuse
6
+ # Cop that denies the use of ActiveRecord methods outside of models.
7
+ class ActiveRecord < RuboCop::Cop::Cop
8
+ MSG = 'This method can only be used inside an ActiveRecord model: ' \
9
+ 'https://gitlab.com/gitlab-org/gitlab-foss/issues/49653'
10
+
11
+ # Various methods from ActiveRecord::Querying that are denied. We
12
+ # exclude some generic ones such as `any?` and `first`, as these may
13
+ # lead to too many false positives, since `Array` also supports these
14
+ # methods.
15
+ #
16
+ # The keys of this Hash are the denied method names. The values are
17
+ # booleans that indicate if the method should only be denied if any
18
+ # arguments are provided.
19
+ NOT_ALLOWED = {
20
+ average: true,
21
+ calculate: true,
22
+ count_by_sql: true,
23
+ create_with: true,
24
+ distinct: false,
25
+ eager_load: true,
26
+ exists?: true,
27
+ find_by: true,
28
+ find_by!: true,
29
+ find_by_sql: true,
30
+ find_each: true,
31
+ find_in_batches: true,
32
+ find_or_create_by: true,
33
+ find_or_create_by!: true,
34
+ find_or_initialize_by: true,
35
+ first!: false,
36
+ first_or_create: true,
37
+ first_or_create!: true,
38
+ first_or_initialize: true,
39
+ from: true,
40
+ group: true,
41
+ having: true,
42
+ ids: false,
43
+ includes: true,
44
+ joins: true,
45
+ lock: false,
46
+ many?: false,
47
+ offset: true,
48
+ order: true,
49
+ pluck: true,
50
+ preload: true,
51
+ readonly: false,
52
+ references: true,
53
+ reorder: true,
54
+ rewhere: true,
55
+ take: false,
56
+ take!: false,
57
+ unscope: false,
58
+ where: false,
59
+ with: true
60
+ }.freeze
61
+
62
+ def on_send(node)
63
+ receiver = node.children[0]
64
+ send_name = node.children[1]
65
+ first_arg = node.children[2]
66
+
67
+ return unless receiver && NOT_ALLOWED.key?(send_name)
68
+
69
+ # If the rule requires an argument to be given, but none are
70
+ # provided, we won't register an offense. This prevents us from
71
+ # adding offenses for `project.group`, while still covering
72
+ # `Project.group(:name)`.
73
+ return if NOT_ALLOWED[send_name] && !first_arg
74
+
75
+ add_offense(node, location: :selector)
76
+ end
77
+ end
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,69 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rubocop
4
+ module Cop
5
+ # This cop makes sure that custom error classes, when empty, are declared
6
+ # with Class.new.
7
+ #
8
+ # @example
9
+ # # bad
10
+ # class FooError < StandardError
11
+ # end
12
+ #
13
+ # # okish
14
+ # class FooError < StandardError; end
15
+ #
16
+ # # good
17
+ # FooError = Class.new(StandardError)
18
+ class CustomErrorClass < RuboCop::Cop::Cop
19
+ MSG = 'Use `Class.new(SuperClass)` to define an empty custom error class.'
20
+
21
+ def on_class(node)
22
+ parent = node.parent_class
23
+ body = node.body
24
+
25
+ return if body
26
+
27
+ parent_klass = class_name_from_node(parent)
28
+
29
+ return unless parent_klass&.to_s&.end_with?('Error')
30
+
31
+ add_offense(node)
32
+ end
33
+
34
+ def autocorrect(node)
35
+ klass = node.identifier
36
+ parent = node.parent_class
37
+
38
+ replacement = "#{class_name_from_node(klass)} = Class.new(#{class_name_from_node(parent)})"
39
+
40
+ lambda do |corrector|
41
+ corrector.replace(node.source_range, replacement)
42
+ end
43
+ end
44
+
45
+ private
46
+
47
+ # The nested constant `Foo::Bar::Baz` looks like:
48
+ #
49
+ # s(:const,
50
+ # s(:const,
51
+ # s(:const, nil, :Foo), :Bar), :Baz)
52
+ #
53
+ # So recurse through that to get the name as written in the source.
54
+ #
55
+ def class_name_from_node(node, suffix = nil)
56
+ return unless node&.type == :const
57
+
58
+ name = node.children[1].to_s
59
+ name = "#{name}::#{suffix}" if suffix
60
+
61
+ if node.children[0]
62
+ class_name_from_node(node.children[0], name)
63
+ else
64
+ name
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../../../gitlab/styles/common/banned_constants'
4
+
5
+ module Rubocop
6
+ module Cop
7
+ module Fips
8
+ class MD5 < RuboCop::Cop::Base
9
+ include Gitlab::Styles::Common::BannedConstants
10
+
11
+ MESSAGE_TEMPLATE = 'MD5 is not FIPS-compliant. Use %{replacement} instead.'
12
+
13
+ REPLACEMENTS = {
14
+ 'OpenSSL::Digest::MD5' => 'OpenSSL::Digest::SHA256',
15
+ 'Digest::MD5' => 'OpenSSL::Digest::SHA256'
16
+ }.freeze
17
+
18
+ def initialize(config = nil, options = nil)
19
+ @message_template = MESSAGE_TEMPLATE
20
+ @replacements = REPLACEMENTS
21
+ @autocorrect = false
22
+ super(config, options)
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../../../gitlab/styles/common/banned_constants'
4
+
5
+ module Rubocop
6
+ module Cop
7
+ module Fips
8
+ class OpenSSL < RuboCop::Cop::Base
9
+ extend RuboCop::Cop::AutoCorrector
10
+ include Gitlab::Styles::Common::BannedConstants
11
+
12
+ MESSAGE_TEMPLATE = 'Usage of this class is not FIPS-compliant. Use %{replacement} instead.'
13
+
14
+ REPLACEMENTS = {
15
+ 'Digest::SHA1' => 'OpenSSL::Digest::SHA1',
16
+ 'Digest::SHA2' => 'OpenSSL::Digest::SHA256',
17
+ 'Digest::SHA256' => 'OpenSSL::Digest::SHA256',
18
+ 'Digest::SHA384' => 'OpenSSL::Digest::SHA384',
19
+ 'Digest::SHA512' => 'OpenSSL::Digest::SHA512'
20
+ }.freeze
21
+
22
+ def initialize(config = nil, options = nil)
23
+ @message_template = MESSAGE_TEMPLATE
24
+ @replacements = REPLACEMENTS
25
+ @autocorrect = true
26
+ super(config, options)
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../../../gitlab/styles/common/banned_constants'
4
+
5
+ module Rubocop
6
+ module Cop
7
+ module Fips
8
+ class SHA1 < RuboCop::Cop::Base
9
+ include Gitlab::Styles::Common::BannedConstants
10
+
11
+ MESSAGE_TEMPLATE = 'SHA1 is likely to become non-compliant in the near future. Use %{replacement} instead.'
12
+
13
+ REPLACEMENTS = {
14
+ 'OpenSSL::Digest::SHA1' => 'OpenSSL::Digest::SHA256',
15
+ 'Digest::SHA1' => 'OpenSSL::Digest::SHA256'
16
+ }.freeze
17
+
18
+ def initialize(config = nil, options = nil)
19
+ @message_template = MESSAGE_TEMPLATE
20
+ @replacements = REPLACEMENTS
21
+ @autocorrect = false
22
+ super(config, options)
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rubocop
4
+ module Cop
5
+ # This cop prevents usage of the `git` and `github` arguments to `gem` in a
6
+ # `Gemfile` in order to avoid additional points of failure beyond
7
+ # rubygems.org.
8
+ class GemFetcher < RuboCop::Cop::Cop
9
+ MSG = 'Do not use gems from git repositories, only use gems from RubyGems.'
10
+
11
+ GIT_KEYS = [:git, :github].freeze
12
+
13
+ def on_send(node)
14
+ return unless gemfile?(node)
15
+
16
+ func_name = node.children[1]
17
+ return unless func_name == :gem
18
+
19
+ node.children.last.each_node(:pair) do |pair|
20
+ key_name = pair.children[0].children[0].to_sym
21
+ add_offense(node, location: pair.source_range) if GIT_KEYS.include?(key_name)
22
+ end
23
+ end
24
+
25
+ private
26
+
27
+ def gemfile?(node)
28
+ node
29
+ .location
30
+ .expression
31
+ .source_buffer
32
+ .name
33
+ .end_with?("Gemfile")
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,18 @@
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 `in_batches`
8
+ class InBatches < RuboCop::Cop::Cop
9
+ MSG = 'Do not use `in_batches`, use `each_batch` from the EachBatch module instead'
10
+
11
+ def on_send(node)
12
+ return unless node.children[1] == :in_batches
13
+
14
+ add_offense(node, location: :selector)
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rubocop
4
+ module Cop
5
+ module InternalAffairs
6
+ # Cop that denies the use of CopHelper.
7
+ class DeprecateCopHelper < RuboCop::Cop::Cop
8
+ MSG = 'Do not use `CopHelper` or methods from it, use improved patterns described in https://www.rubydoc.info/gems/rubocop/RuboCop/RSpec/ExpectOffense'
9
+
10
+ def_node_matcher :cop_helper, <<~PATTERN
11
+ (send nil? ${:include :extend :prepend}
12
+ (const _ {:CopHelper}))
13
+ PATTERN
14
+
15
+ def_node_search :cop_helper_method, <<~PATTERN
16
+ (send nil? {:inspect_source :inspect_source_file :parse_source :autocorrect_source_file :autocorrect_source :_investigate} ...)
17
+ PATTERN
18
+
19
+ def_node_search :cop_helper_method_on_instance, <<~PATTERN
20
+ (send (send nil? _) {:messages :highlights :offenses} ...)
21
+ PATTERN
22
+
23
+ def on_send(node)
24
+ cop_helper(node) do
25
+ add_offense(node)
26
+ end
27
+
28
+ cop_helper_method(node) do
29
+ add_offense(node)
30
+ end
31
+
32
+ cop_helper_method_on_instance(node) do
33
+ add_offense(node)
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
@@ -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