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
@@ -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