rubocop-rspec 1.38.0 → 1.42.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 (81) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +44 -0
  3. data/CODE_OF_CONDUCT.md +17 -0
  4. data/README.md +1 -61
  5. data/config/default.yml +147 -17
  6. data/lib/rubocop-rspec.rb +3 -1
  7. data/lib/rubocop/cop/rspec/align_left_let_brace.rb +11 -18
  8. data/lib/rubocop/cop/rspec/align_right_let_brace.rb +11 -18
  9. data/lib/rubocop/cop/rspec/be.rb +1 -1
  10. data/lib/rubocop/cop/rspec/be_eql.rb +5 -5
  11. data/lib/rubocop/cop/rspec/capybara/current_path_expectation.rb +18 -16
  12. data/lib/rubocop/cop/rspec/capybara/feature_methods.rb +8 -9
  13. data/lib/rubocop/cop/rspec/capybara/visibility_matcher.rb +69 -0
  14. data/lib/rubocop/cop/rspec/context_method.rb +5 -7
  15. data/lib/rubocop/cop/rspec/cop.rb +9 -29
  16. data/lib/rubocop/cop/rspec/describe_class.rb +20 -5
  17. data/lib/rubocop/cop/rspec/describe_method.rb +0 -1
  18. data/lib/rubocop/cop/rspec/described_class.rb +10 -7
  19. data/lib/rubocop/cop/rspec/dialect.rb +4 -11
  20. data/lib/rubocop/cop/rspec/empty_hook.rb +46 -0
  21. data/lib/rubocop/cop/rspec/empty_line_after_example.rb +5 -3
  22. data/lib/rubocop/cop/rspec/empty_line_after_example_group.rb +5 -5
  23. data/lib/rubocop/cop/rspec/empty_line_after_final_let.rb +4 -1
  24. data/lib/rubocop/cop/rspec/empty_line_after_hook.rb +5 -5
  25. data/lib/rubocop/cop/rspec/empty_line_after_subject.rb +4 -1
  26. data/lib/rubocop/cop/rspec/example_wording.rb +6 -7
  27. data/lib/rubocop/cop/rspec/expect_actual.rb +7 -10
  28. data/lib/rubocop/cop/rspec/expect_change.rb +9 -34
  29. data/lib/rubocop/cop/rspec/expect_in_hook.rb +2 -2
  30. data/lib/rubocop/cop/rspec/expect_output.rb +1 -1
  31. data/lib/rubocop/cop/rspec/factory_bot/attribute_defined_statically.rb +23 -20
  32. data/lib/rubocop/cop/rspec/factory_bot/create_list.rb +10 -16
  33. data/lib/rubocop/cop/rspec/factory_bot/factory_class_name.rb +6 -7
  34. data/lib/rubocop/cop/rspec/file_path.rb +32 -4
  35. data/lib/rubocop/cop/rspec/hook_argument.rb +11 -17
  36. data/lib/rubocop/cop/rspec/hooks_before_examples.rb +9 -28
  37. data/lib/rubocop/cop/rspec/implicit_expect.rb +6 -14
  38. data/lib/rubocop/cop/rspec/implicit_subject.rb +8 -5
  39. data/lib/rubocop/cop/rspec/instance_spy.rb +17 -11
  40. data/lib/rubocop/cop/rspec/instance_variable.rb +3 -7
  41. data/lib/rubocop/cop/rspec/invalid_predicate_matcher.rb +2 -5
  42. data/lib/rubocop/cop/rspec/it_behaves_like.rb +4 -5
  43. data/lib/rubocop/cop/rspec/leading_subject.rb +12 -19
  44. data/lib/rubocop/cop/rspec/leaky_constant_declaration.rb +1 -4
  45. data/lib/rubocop/cop/rspec/let_before_examples.rb +9 -25
  46. data/lib/rubocop/cop/rspec/let_setup.rb +15 -3
  47. data/lib/rubocop/cop/rspec/message_chain.rb +6 -5
  48. data/lib/rubocop/cop/rspec/message_expectation.rb +1 -1
  49. data/lib/rubocop/cop/rspec/message_spies.rb +1 -2
  50. data/lib/rubocop/cop/rspec/multiple_subjects.rb +17 -18
  51. data/lib/rubocop/cop/rspec/named_subject.rb +7 -7
  52. data/lib/rubocop/cop/rspec/nested_groups.rb +9 -10
  53. data/lib/rubocop/cop/rspec/not_to_not.rb +4 -5
  54. data/lib/rubocop/cop/rspec/predicate_matcher.rb +25 -55
  55. data/lib/rubocop/cop/rspec/rails/http_status.rb +6 -8
  56. data/lib/rubocop/cop/rspec/receive_counts.rb +14 -16
  57. data/lib/rubocop/cop/rspec/receive_never.rb +10 -10
  58. data/lib/rubocop/cop/rspec/repeated_example_group_body.rb +11 -1
  59. data/lib/rubocop/cop/rspec/return_from_stub.rb +11 -21
  60. data/lib/rubocop/cop/rspec/scattered_let.rb +11 -1
  61. data/lib/rubocop/cop/rspec/shared_context.rb +7 -20
  62. data/lib/rubocop/cop/rspec/shared_examples.rb +6 -8
  63. data/lib/rubocop/cop/rspec/single_argument_message_chain.rb +14 -17
  64. data/lib/rubocop/cop/rspec/subject_stub.rb +23 -51
  65. data/lib/rubocop/cop/rspec/variable_definition.rb +56 -0
  66. data/lib/rubocop/cop/rspec/variable_name.rb +47 -0
  67. data/lib/rubocop/cop/rspec/yield.rb +13 -10
  68. data/lib/rubocop/cop/rspec_cops.rb +5 -1
  69. data/lib/rubocop/rspec/blank_line_separation.rb +0 -8
  70. data/lib/rubocop/rspec/corrector/move_node.rb +52 -0
  71. data/lib/rubocop/rspec/description_extractor.rb +2 -6
  72. data/lib/rubocop/rspec/example.rb +1 -1
  73. data/lib/rubocop/rspec/example_group.rb +21 -49
  74. data/lib/rubocop/rspec/factory_bot.rb +7 -1
  75. data/lib/rubocop/rspec/language.rb +8 -0
  76. data/lib/rubocop/rspec/language/node_pattern.rb +5 -1
  77. data/lib/rubocop/rspec/top_level_group.rb +44 -0
  78. data/lib/rubocop/rspec/variable.rb +16 -0
  79. data/lib/rubocop/rspec/version.rb +1 -1
  80. metadata +17 -10
  81. data/lib/rubocop/rspec/util.rb +0 -19
@@ -11,18 +11,20 @@ require_relative 'rubocop/rspec/inject'
11
11
  require_relative 'rubocop/rspec/node'
12
12
  require_relative 'rubocop/rspec/top_level_describe'
13
13
  require_relative 'rubocop/rspec/wording'
14
- require_relative 'rubocop/rspec/util'
15
14
  require_relative 'rubocop/rspec/language'
16
15
  require_relative 'rubocop/rspec/language/node_pattern'
16
+ require_relative 'rubocop/rspec/top_level_group'
17
17
  require_relative 'rubocop/rspec/concept'
18
18
  require_relative 'rubocop/rspec/example_group'
19
19
  require_relative 'rubocop/rspec/example'
20
20
  require_relative 'rubocop/rspec/hook'
21
+ require_relative 'rubocop/rspec/variable'
21
22
  require_relative 'rubocop/cop/rspec/cop'
22
23
  require_relative 'rubocop/rspec/align_let_brace'
23
24
  require_relative 'rubocop/rspec/factory_bot'
24
25
  require_relative 'rubocop/rspec/final_end_location'
25
26
  require_relative 'rubocop/rspec/blank_line_separation'
27
+ require_relative 'rubocop/rspec/corrector/move_node'
26
28
 
27
29
  RuboCop::RSpec::Inject.defaults!
28
30
 
@@ -18,35 +18,28 @@ module RuboCop
18
18
  # let(:a) { b }
19
19
  #
20
20
  class AlignLeftLetBrace < Cop
21
+ extend AutoCorrector
22
+
21
23
  MSG = 'Align left let brace'
22
24
 
23
25
  def self.autocorrect_incompatible_with
24
26
  [Layout::ExtraSpacing]
25
27
  end
26
28
 
27
- def investigate(_processed_source)
29
+ def on_new_investigation
28
30
  return if processed_source.blank?
29
31
 
30
- token_aligner.offending_tokens.each do |let|
31
- add_offense(let, location: :begin)
32
- end
33
- end
32
+ token_aligner =
33
+ RuboCop::RSpec::AlignLetBrace.new(processed_source.ast, :begin)
34
34
 
35
- def autocorrect(let)
36
- lambda do |corrector|
37
- corrector.insert_before(
38
- let.loc.begin,
39
- token_aligner.indent_for(let)
40
- )
35
+ token_aligner.offending_tokens.each do |let|
36
+ add_offense(let.loc.begin) do |corrector|
37
+ corrector.insert_before(
38
+ let.loc.begin, token_aligner.indent_for(let)
39
+ )
40
+ end
41
41
  end
42
42
  end
43
-
44
- private
45
-
46
- def token_aligner
47
- @token_aligner ||=
48
- RuboCop::RSpec::AlignLetBrace.new(processed_source.ast, :begin)
49
- end
50
43
  end
51
44
  end
52
45
  end
@@ -18,35 +18,28 @@ module RuboCop
18
18
  # let(:a) { b }
19
19
  #
20
20
  class AlignRightLetBrace < Cop
21
+ extend AutoCorrector
22
+
21
23
  MSG = 'Align right let brace'
22
24
 
23
25
  def self.autocorrect_incompatible_with
24
26
  [Layout::ExtraSpacing]
25
27
  end
26
28
 
27
- def investigate(_processed_source)
29
+ def on_new_investigation
28
30
  return if processed_source.blank?
29
31
 
30
- token_aligner.offending_tokens.each do |let|
31
- add_offense(let, location: :end)
32
- end
33
- end
32
+ token_aligner =
33
+ RuboCop::RSpec::AlignLetBrace.new(processed_source.ast, :end)
34
34
 
35
- def autocorrect(let)
36
- lambda do |corrector|
37
- corrector.insert_before(
38
- let.loc.end,
39
- token_aligner.indent_for(let)
40
- )
35
+ token_aligner.offending_tokens.each do |let|
36
+ add_offense(let.loc.end) do |corrector|
37
+ corrector.insert_before(
38
+ let.loc.end, token_aligner.indent_for(let)
39
+ )
40
+ end
41
41
  end
42
42
  end
43
-
44
- private
45
-
46
- def token_aligner
47
- @token_aligner ||=
48
- RuboCop::RSpec::AlignLetBrace.new(processed_source.ast, :end)
49
- end
50
43
  end
51
44
  end
52
45
  end
@@ -28,7 +28,7 @@ module RuboCop
28
28
 
29
29
  def on_send(node)
30
30
  be_without_args(node) do |matcher|
31
- add_offense(matcher, location: :selector)
31
+ add_offense(matcher.loc.selector)
32
32
  end
33
33
  end
34
34
  end
@@ -36,6 +36,8 @@ module RuboCop
36
36
  # coerce objects for comparison.
37
37
  #
38
38
  class BeEql < Cop
39
+ extend AutoCorrector
40
+
39
41
  MSG = 'Prefer `be` over `eql`.'
40
42
 
41
43
  def_node_matcher :eql_type_with_identity, <<-PATTERN
@@ -44,13 +46,11 @@ module RuboCop
44
46
 
45
47
  def on_send(node)
46
48
  eql_type_with_identity(node) do |eql|
47
- add_offense(eql, location: :selector)
49
+ add_offense(eql.loc.selector) do |corrector|
50
+ corrector.replace(eql.loc.selector, 'be')
51
+ end
48
52
  end
49
53
  end
50
-
51
- def autocorrect(node)
52
- ->(corrector) { corrector.replace(node.loc.selector, 'be') }
53
- end
54
54
  end
55
55
  end
56
56
  end
@@ -24,6 +24,8 @@ module RuboCop
24
24
  # expect(page).to have_current_path(/widgets/)
25
25
  #
26
26
  class CurrentPathExpectation < Cop
27
+ extend AutoCorrector
28
+
27
29
  MSG = 'Do not set an RSpec expectation on `current_path` in ' \
28
30
  'Capybara feature specs - instead, use the ' \
29
31
  '`have_current_path` matcher on `page`'
@@ -47,30 +49,30 @@ module RuboCop
47
49
 
48
50
  def on_send(node)
49
51
  expectation_set_on_current_path(node) do
50
- add_offense(node, location: :selector)
52
+ add_offense(node.loc.selector) do |corrector|
53
+ next unless node.chained?
54
+
55
+ autocorrect(corrector, node)
56
+ end
51
57
  end
52
58
  end
53
59
 
54
- def autocorrect(node)
55
- lambda do |corrector|
56
- return unless node.chained?
60
+ private
57
61
 
58
- as_is_matcher(node.parent) do |to_sym, matcher_node|
59
- rewrite_expectation(corrector, node, to_sym, matcher_node)
60
- end
62
+ def autocorrect(corrector, node)
63
+ as_is_matcher(node.parent) do |to_sym, matcher_node|
64
+ rewrite_expectation(corrector, node, to_sym, matcher_node)
65
+ end
61
66
 
62
- regexp_str_matcher(node.parent) do |to_sym, matcher_node, regexp|
63
- rewrite_expectation(corrector, node, to_sym, matcher_node)
64
- convert_regexp_str_to_literal(corrector, matcher_node, regexp)
65
- end
67
+ regexp_str_matcher(node.parent) do |to_sym, matcher_node, regexp|
68
+ rewrite_expectation(corrector, node, to_sym, matcher_node)
69
+ convert_regexp_str_to_literal(corrector, matcher_node, regexp)
66
70
  end
67
71
  end
68
72
 
69
- private
70
-
71
73
  def rewrite_expectation(corrector, node, to_symbol, matcher_node)
72
74
  current_path_node = node.first_argument
73
- corrector.replace(current_path_node.loc.expression, 'page')
75
+ corrector.replace(current_path_node, 'page')
74
76
  corrector.replace(node.parent.loc.selector, 'to')
75
77
  matcher_method = if to_symbol == :to
76
78
  'have_current_path'
@@ -84,7 +86,7 @@ module RuboCop
84
86
  def convert_regexp_str_to_literal(corrector, matcher_node, regexp_str)
85
87
  str_node = matcher_node.first_argument
86
88
  regexp_expr = Regexp.new(regexp_str).inspect
87
- corrector.replace(str_node.loc.expression, regexp_expr)
89
+ corrector.replace(str_node, regexp_expr)
88
90
  end
89
91
 
90
92
  # `have_current_path` with no options will include the querystring
@@ -97,7 +99,7 @@ module RuboCop
97
99
  return if %i[regexp str].include?(expectation_last_child.type)
98
100
 
99
101
  corrector.insert_after(
100
- expectation_last_child.loc.expression,
102
+ expectation_last_child,
101
103
  ', ignore_query: true'
102
104
  )
103
105
  end
@@ -41,6 +41,8 @@ module RuboCop
41
41
  # end
42
42
  # end
43
43
  class FeatureMethods < Cop
44
+ extend AutoCorrector
45
+
44
46
  MSG = 'Use `%<replacement>s` instead of `%<method>s`.'
45
47
 
46
48
  # https://git.io/v7Kwr
@@ -71,18 +73,15 @@ module RuboCop
71
73
  feature_method(node) do |send_node, match|
72
74
  next if enabled?(match)
73
75
 
74
- add_offense(
75
- send_node,
76
- location: :selector,
77
- message: format(MSG, method: match, replacement: MAP[match])
78
- )
76
+ add_offense(send_node.loc.selector) do |corrector|
77
+ corrector.replace(send_node.loc.selector, MAP[match].to_s)
78
+ end
79
79
  end
80
80
  end
81
81
 
82
- def autocorrect(node)
83
- lambda do |corrector|
84
- corrector.replace(node.loc.selector, MAP[node.method_name].to_s)
85
- end
82
+ def message(range)
83
+ name = range.source.to_sym
84
+ format(MSG, method: name, replacement: MAP[name])
86
85
  end
87
86
 
88
87
  private
@@ -0,0 +1,69 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module RSpec
6
+ module Capybara
7
+ # Checks for boolean visibility in capybara finders.
8
+ #
9
+ # Capybara lets you find elements that match a certain visibility using
10
+ # the `:visible` option. `:visible` accepts both boolean and symbols as
11
+ # values, however using booleans can have unwanted effects. `visible:
12
+ # false` does not find just invisible elements, but both visible and
13
+ # invisible elements. For expressiveness and clarity, use one of the
14
+ # symbol values, `:all`, `:hidden` or `:visible`.
15
+ # (https://www.rubydoc.info/gems/capybara/Capybara%2FNode%2FFinders:all)
16
+ #
17
+ # @example
18
+ #
19
+ # # bad
20
+ # expect(page).to have_selector('.foo', visible: false)
21
+ # expect(page).to have_css('.foo', visible: true)
22
+ # expect(page).to have_link('my link', visible: false)
23
+ #
24
+ # # good
25
+ # expect(page).to have_selector('.foo', visible: :visible)
26
+ # expect(page).to have_css('.foo', visible: :all)
27
+ # expect(page).to have_link('my link', visible: :hidden)
28
+ #
29
+ class VisibilityMatcher < Cop
30
+ MSG_FALSE = 'Use `:all` or `:hidden` instead of `false`.'
31
+ MSG_TRUE = 'Use `:visible` instead of `true`.'
32
+ CAPYBARA_MATCHER_METHODS = %i[
33
+ have_selector
34
+ have_css
35
+ have_xpath
36
+ have_link
37
+ have_button
38
+ have_field
39
+ have_select
40
+ have_table
41
+ have_checked_field
42
+ have_unchecked_field
43
+ have_text
44
+ have_content
45
+ ].freeze
46
+
47
+ def_node_matcher :visible_true?, <<~PATTERN
48
+ (send nil? #capybara_matcher? ... (hash <$(pair (sym :visible) true) ...>))
49
+ PATTERN
50
+
51
+ def_node_matcher :visible_false?, <<~PATTERN
52
+ (send nil? #capybara_matcher? ... (hash <$(pair (sym :visible) false) ...>))
53
+ PATTERN
54
+
55
+ def on_send(node)
56
+ visible_false?(node) { |arg| add_offense(arg, message: MSG_FALSE) }
57
+ visible_true?(node) { |arg| add_offense(arg, message: MSG_TRUE) }
58
+ end
59
+
60
+ private
61
+
62
+ def capybara_matcher?(method_name)
63
+ CAPYBARA_MATCHER_METHODS.include? method_name
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
@@ -24,6 +24,8 @@ module RuboCop
24
24
  # # ...
25
25
  # end
26
26
  class ContextMethod < Cop
27
+ extend AutoCorrector
28
+
27
29
  MSG = 'Use `describe` for testing methods.'
28
30
 
29
31
  def_node_matcher :context_method, <<-PATTERN
@@ -32,13 +34,9 @@ module RuboCop
32
34
 
33
35
  def on_block(node)
34
36
  context_method(node) do |context|
35
- add_offense(context)
36
- end
37
- end
38
-
39
- def autocorrect(node)
40
- lambda do |corrector|
41
- corrector.replace(node.parent.loc.selector, 'describe')
37
+ add_offense(context) do |corrector|
38
+ corrector.replace(node.send_node.loc.selector, 'describe')
39
+ end
42
40
  end
43
41
  end
44
42
 
@@ -1,29 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module RuboCop
4
- module Cop # rubocop:disable Style/Documentation
5
- WorkaroundCop = Cop.dup
6
-
7
- # Clone of the the normal RuboCop::Cop::Cop class so we can rewrite
8
- # the inherited method without breaking functionality
9
- class WorkaroundCop
10
- # Remove the Cop.inherited method to be a noop. Our RSpec::Cop
11
- # class will invoke the inherited hook instead
12
- class << self
13
- undef inherited
14
- def inherited(*) end
15
- end
16
-
17
- # Special case `Module#<` so that the rspec support rubocop exports
18
- # is compatible with our subclass
19
- def self.<(other)
20
- other.equal?(RuboCop::Cop::Cop) || super
21
- end
22
- end
23
- private_constant(:WorkaroundCop)
24
-
4
+ module Cop
25
5
  module RSpec
26
- # @abstract parent class to rspec cops
6
+ # @abstract parent class to RSpec cops
27
7
  #
28
8
  # The criteria for whether rubocop-rspec analyzes a certain ruby file
29
9
  # is configured via `AllCops/RSpec`. For example, if you want to
@@ -31,13 +11,13 @@ module RuboCop
31
11
  # then you could add this to your configuration:
32
12
  #
33
13
  # @example configuring analyzed paths
34
- #
35
- # AllCops:
36
- # RSpec:
37
- # Patterns:
38
- # - '_test.rb$'
39
- # - '(?:^|/)test/'
40
- class Cop < WorkaroundCop
14
+ # # .rubocop.yml
15
+ # # AllCops:
16
+ # # RSpec:
17
+ # # Patterns:
18
+ # # - '_test.rb$'
19
+ # # - '(?:^|/)test/'
20
+ class Cop < ::RuboCop::Cop::Base
41
21
  include RuboCop::RSpec::Language
42
22
  include RuboCop::RSpec::Language::NodePattern
43
23
 
@@ -12,6 +12,11 @@ module RuboCop
12
12
  #
13
13
  # # good
14
14
  # describe TestedClass do
15
+ # subject { described_class }
16
+ # end
17
+ #
18
+ # describe 'TestedClass::VERSION' do
19
+ # subject { Object.const_get(self.class.description) }
15
20
  # end
16
21
  #
17
22
  # describe "A feature example", type: :feature do
@@ -38,18 +43,28 @@ module RuboCop
38
43
  def_node_matcher :rails_metadata?, <<-PATTERN
39
44
  (pair
40
45
  (sym :type)
41
- (sym {:request :feature :system :routing :view})
46
+ (sym {
47
+ :channel :controller :helper :job :mailer :model :request
48
+ :routing :view :feature :system :mailbox
49
+ }
50
+ )
42
51
  )
43
52
  PATTERN
44
53
 
45
- def_node_matcher :shared_group?, SharedGroups::ALL.block_pattern
46
-
47
- def on_top_level_describe(node, args)
54
+ def on_top_level_describe(node, (described_value, _))
48
55
  return if shared_group?(root_node)
49
56
  return if valid_describe?(node)
50
57
  return if describe_with_rails_metadata?(node)
58
+ return if string_constant_describe?(described_value)
59
+
60
+ add_offense(described_value)
61
+ end
62
+
63
+ private
51
64
 
52
- add_offense(args.first)
65
+ def string_constant_describe?(described_value)
66
+ described_value.str_type? &&
67
+ described_value.value =~ /^((::)?[A-Z]\w*)+$/
53
68
  end
54
69
  end
55
70
  end