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
@@ -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
@@ -0,0 +1,120 @@
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 for common mistakes in example descriptions.
10
+ #
11
+ # This cop will correct docstrings that begin/end with space or words that start with a capital letter.
12
+ #
13
+ # @see https://gitlab.com/gitlab-org/gitlab/-/merge_requests/46336#note_442669518
14
+ #
15
+ # @example
16
+ # # bad
17
+ # it 'Does something' do
18
+ # end
19
+ #
20
+ # # good
21
+ # it 'does nothing' do
22
+ # end
23
+ #
24
+ # @example
25
+ # # bad
26
+ # it ' does something' do
27
+ # end
28
+ #
29
+ # # good
30
+ # it 'does something' do
31
+ # end
32
+ #
33
+ # @example
34
+ # # bad
35
+ # it 'does something ' do
36
+ # end
37
+ #
38
+ # # good
39
+ # it 'does something' do
40
+ # end
41
+ #
42
+ # @example
43
+ # # bad
44
+ # it ' does something ' do
45
+ # end
46
+ #
47
+ # # good
48
+ # it 'does something' do
49
+ # end
50
+ class ExampleStartingCharacter < Base
51
+ extend RuboCop::Cop::AutoCorrector
52
+
53
+ MSG = 'Only start words with lowercase alpha with no leading/trailing spaces when describing your tests.'
54
+
55
+ def_node_matcher :it_description, <<-PATTERN
56
+ (block (send _ :it ${
57
+ (str $_)
58
+ (dstr (str $_ ) ...)
59
+ } ...) ...)
60
+ PATTERN
61
+
62
+ def on_block(node)
63
+ it_description(node) do |description_node, _message|
64
+ add_wording_offense(description_node, MSG) if invalid_description?(text(description_node))
65
+ end
66
+ end
67
+
68
+ private
69
+
70
+ def add_wording_offense(node, message)
71
+ docstring = docstring(node)
72
+ add_offense(docstring, message: message) do |corrector|
73
+ corrector.replace(docstring, replacement_text(node))
74
+ end
75
+ end
76
+
77
+ def docstring(node)
78
+ expr = node.loc.expression
79
+
80
+ Parser::Source::Range.new(
81
+ expr.source_buffer,
82
+ expr.begin_pos + 1,
83
+ expr.end_pos - 1
84
+ )
85
+ end
86
+
87
+ def invalid_description?(message)
88
+ message.match?(/(^([A-Z]{1}[a-z]+\s|\s)|\s$)/)
89
+ end
90
+
91
+ def replacement_text(node)
92
+ text = text(node)
93
+
94
+ text.strip!
95
+
96
+ text = downcase_first_letter(text) if invalid_description?(text)
97
+
98
+ text
99
+ end
100
+
101
+ # Recursive processing is required to process nested dstr nodes
102
+ # that is the case for \-separated multiline strings with interpolation.
103
+ def text(node)
104
+ case node.type
105
+ when :dstr
106
+ node.node_parts.map { |child_node| text(child_node) }.join
107
+ when :str
108
+ node.value
109
+ when :begin
110
+ node.source
111
+ end
112
+ end
113
+
114
+ def downcase_first_letter(str)
115
+ str[0].downcase + str[1..]
116
+ end
117
+ end
118
+ end
119
+ end
120
+ end
@@ -0,0 +1,44 @@
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
+ # This cop checks for unused parameters to the `have_link` matcher.
10
+ #
11
+ # @example
12
+ #
13
+ # # bad
14
+ # expect(page).to have_link('Link', 'https://example.com')
15
+ #
16
+ # # good
17
+ # expect(page).to have_link('Link', href: 'https://example.com')
18
+ # expect(page).to have_link('Example')
19
+ class HaveLinkParameters < Base
20
+ extend RuboCop::Cop::AutoCorrector
21
+
22
+ MESSAGE = "The second argument to `have_link` should be a Hash."
23
+
24
+ def_node_matcher :unused_parameters?, <<~PATTERN
25
+ (send nil? :have_link
26
+ _ !{hash nil}
27
+ )
28
+ PATTERN
29
+
30
+ def on_send(node)
31
+ return unless unused_parameters?(node)
32
+
33
+ location = node.arguments[1..]
34
+ .map(&:source_range)
35
+ .reduce(:join)
36
+
37
+ add_offense(location, message: MESSAGE) do |corrector|
38
+ corrector.insert_after(location.end, "\n")
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end