rubocop-rspec 1.38.1 → 1.43.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (107) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +57 -0
  3. data/CODE_OF_CONDUCT.md +17 -0
  4. data/README.md +1 -61
  5. data/config/default.yml +159 -19
  6. data/lib/rubocop-rspec.rb +5 -2
  7. data/lib/rubocop/cop/rspec/align_left_let_brace.rb +12 -19
  8. data/lib/rubocop/cop/rspec/align_right_let_brace.rb +12 -19
  9. data/lib/rubocop/cop/rspec/any_instance.rb +1 -1
  10. data/lib/rubocop/cop/rspec/around_block.rb +1 -1
  11. data/lib/rubocop/cop/rspec/base.rb +74 -0
  12. data/lib/rubocop/cop/rspec/be.rb +2 -2
  13. data/lib/rubocop/cop/rspec/be_eql.rb +6 -6
  14. data/lib/rubocop/cop/rspec/before_after_all.rb +1 -1
  15. data/lib/rubocop/cop/rspec/capybara/current_path_expectation.rb +19 -17
  16. data/lib/rubocop/cop/rspec/capybara/feature_methods.rb +14 -12
  17. data/lib/rubocop/cop/rspec/capybara/visibility_matcher.rb +69 -0
  18. data/lib/rubocop/cop/rspec/context_method.rb +7 -9
  19. data/lib/rubocop/cop/rspec/context_wording.rb +3 -3
  20. data/lib/rubocop/cop/rspec/cop.rb +3 -87
  21. data/lib/rubocop/cop/rspec/describe_class.rb +29 -23
  22. data/lib/rubocop/cop/rspec/describe_method.rb +14 -7
  23. data/lib/rubocop/cop/rspec/describe_symbol.rb +2 -2
  24. data/lib/rubocop/cop/rspec/described_class.rb +12 -9
  25. data/lib/rubocop/cop/rspec/described_class_module_wrapping.rb +1 -1
  26. data/lib/rubocop/cop/rspec/dialect.rb +5 -12
  27. data/lib/rubocop/cop/rspec/empty_example_group.rb +91 -7
  28. data/lib/rubocop/cop/rspec/empty_hook.rb +46 -0
  29. data/lib/rubocop/cop/rspec/empty_line_after_example.rb +5 -7
  30. data/lib/rubocop/cop/rspec/empty_line_after_example_group.rb +5 -9
  31. data/lib/rubocop/cop/rspec/empty_line_after_final_let.rb +8 -8
  32. data/lib/rubocop/cop/rspec/empty_line_after_hook.rb +5 -9
  33. data/lib/rubocop/cop/rspec/empty_line_after_subject.rb +6 -6
  34. data/lib/rubocop/cop/rspec/example_length.rb +1 -1
  35. data/lib/rubocop/cop/rspec/example_without_description.rb +1 -1
  36. data/lib/rubocop/cop/rspec/example_wording.rb +10 -11
  37. data/lib/rubocop/cop/rspec/expect_actual.rb +8 -11
  38. data/lib/rubocop/cop/rspec/expect_change.rb +10 -35
  39. data/lib/rubocop/cop/rspec/expect_in_hook.rb +3 -3
  40. data/lib/rubocop/cop/rspec/expect_output.rb +2 -2
  41. data/lib/rubocop/cop/rspec/factory_bot/attribute_defined_statically.rb +24 -21
  42. data/lib/rubocop/cop/rspec/factory_bot/create_list.rb +20 -22
  43. data/lib/rubocop/cop/rspec/factory_bot/factory_class_name.rb +7 -8
  44. data/lib/rubocop/cop/rspec/file_path.rb +57 -21
  45. data/lib/rubocop/cop/rspec/focus.rb +7 -11
  46. data/lib/rubocop/cop/rspec/hook_argument.rb +16 -23
  47. data/lib/rubocop/cop/rspec/hooks_before_examples.rb +10 -29
  48. data/lib/rubocop/cop/rspec/implicit_block_expectation.rb +1 -1
  49. data/lib/rubocop/cop/rspec/implicit_expect.rb +7 -15
  50. data/lib/rubocop/cop/rspec/implicit_subject.rb +16 -11
  51. data/lib/rubocop/cop/rspec/instance_spy.rb +18 -12
  52. data/lib/rubocop/cop/rspec/instance_variable.rb +4 -8
  53. data/lib/rubocop/cop/rspec/invalid_predicate_matcher.rb +3 -6
  54. data/lib/rubocop/cop/rspec/it_behaves_like.rb +5 -6
  55. data/lib/rubocop/cop/rspec/iterated_expectation.rb +1 -1
  56. data/lib/rubocop/cop/rspec/leading_subject.rb +22 -26
  57. data/lib/rubocop/cop/rspec/leaky_constant_declaration.rb +2 -5
  58. data/lib/rubocop/cop/rspec/let_before_examples.rb +10 -26
  59. data/lib/rubocop/cop/rspec/let_setup.rb +21 -6
  60. data/lib/rubocop/cop/rspec/message_chain.rb +7 -6
  61. data/lib/rubocop/cop/rspec/message_expectation.rb +2 -2
  62. data/lib/rubocop/cop/rspec/message_spies.rb +2 -3
  63. data/lib/rubocop/cop/rspec/missing_example_group_argument.rb +1 -1
  64. data/lib/rubocop/cop/rspec/multiple_describes.rb +11 -8
  65. data/lib/rubocop/cop/rspec/multiple_expectations.rb +7 -11
  66. data/lib/rubocop/cop/rspec/multiple_memoized_helpers.rb +148 -0
  67. data/lib/rubocop/cop/rspec/multiple_subjects.rb +18 -19
  68. data/lib/rubocop/cop/rspec/named_subject.rb +8 -8
  69. data/lib/rubocop/cop/rspec/nested_groups.rb +12 -13
  70. data/lib/rubocop/cop/rspec/not_to_not.rb +5 -6
  71. data/lib/rubocop/cop/rspec/overwriting_setup.rb +1 -1
  72. data/lib/rubocop/cop/rspec/pending.rb +1 -1
  73. data/lib/rubocop/cop/rspec/predicate_matcher.rb +32 -69
  74. data/lib/rubocop/cop/rspec/rails/http_status.rb +7 -9
  75. data/lib/rubocop/cop/rspec/receive_counts.rb +15 -17
  76. data/lib/rubocop/cop/rspec/receive_never.rb +12 -12
  77. data/lib/rubocop/cop/rspec/repeated_description.rb +1 -1
  78. data/lib/rubocop/cop/rspec/repeated_example.rb +2 -2
  79. data/lib/rubocop/cop/rspec/repeated_example_group_body.rb +12 -2
  80. data/lib/rubocop/cop/rspec/repeated_example_group_description.rb +1 -1
  81. data/lib/rubocop/cop/rspec/return_from_stub.rb +12 -22
  82. data/lib/rubocop/cop/rspec/scattered_let.rb +12 -2
  83. data/lib/rubocop/cop/rspec/scattered_setup.rb +1 -1
  84. data/lib/rubocop/cop/rspec/shared_context.rb +8 -21
  85. data/lib/rubocop/cop/rspec/shared_examples.rb +7 -9
  86. data/lib/rubocop/cop/rspec/single_argument_message_chain.rb +15 -18
  87. data/lib/rubocop/cop/rspec/subject_stub.rb +25 -53
  88. data/lib/rubocop/cop/rspec/unspecified_exception.rb +1 -1
  89. data/lib/rubocop/cop/rspec/variable_definition.rb +56 -0
  90. data/lib/rubocop/cop/rspec/variable_name.rb +66 -0
  91. data/lib/rubocop/cop/rspec/verified_doubles.rb +1 -1
  92. data/lib/rubocop/cop/rspec/void_expect.rb +1 -1
  93. data/lib/rubocop/cop/rspec/yield.rb +14 -11
  94. data/lib/rubocop/cop/rspec_cops.rb +6 -1
  95. data/lib/rubocop/rspec/corrector/move_node.rb +54 -0
  96. data/lib/rubocop/rspec/description_extractor.rb +2 -6
  97. data/lib/rubocop/rspec/{blank_line_separation.rb → empty_line_separation.rb} +13 -10
  98. data/lib/rubocop/rspec/example_group.rb +21 -49
  99. data/lib/rubocop/rspec/factory_bot.rb +7 -1
  100. data/lib/rubocop/rspec/language.rb +13 -3
  101. data/lib/rubocop/rspec/language/node_pattern.rb +11 -2
  102. data/lib/rubocop/rspec/top_level_describe.rb +2 -2
  103. data/lib/rubocop/rspec/top_level_group.rb +55 -0
  104. data/lib/rubocop/rspec/variable.rb +16 -0
  105. data/lib/rubocop/rspec/version.rb +1 -1
  106. metadata +36 -13
  107. data/lib/rubocop/rspec/util.rb +0 -19
@@ -11,18 +11,21 @@ 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'
22
+ require_relative 'rubocop/cop/rspec/base'
21
23
  require_relative 'rubocop/cop/rspec/cop'
22
24
  require_relative 'rubocop/rspec/align_let_brace'
23
25
  require_relative 'rubocop/rspec/factory_bot'
24
26
  require_relative 'rubocop/rspec/final_end_location'
25
- require_relative 'rubocop/rspec/blank_line_separation'
27
+ require_relative 'rubocop/rspec/empty_line_separation'
28
+ require_relative 'rubocop/rspec/corrector/move_node'
26
29
 
27
30
  RuboCop::RSpec::Inject.defaults!
28
31
 
@@ -17,36 +17,29 @@ module RuboCop
17
17
  # let(:baz) { bar }
18
18
  # let(:a) { b }
19
19
  #
20
- class AlignLeftLetBrace < Cop
20
+ class AlignLeftLetBrace < Base
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
@@ -17,36 +17,29 @@ module RuboCop
17
17
  # let(:baz) { bar }
18
18
  # let(:a) { b }
19
19
  #
20
- class AlignRightLetBrace < Cop
20
+ class AlignRightLetBrace < Base
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
@@ -22,7 +22,7 @@ module RuboCop
22
22
  # allow(my_instance).to receive(:foo)
23
23
  # end
24
24
  # end
25
- class AnyInstance < Cop
25
+ class AnyInstance < Base
26
26
  MSG = 'Avoid stubbing using `%<method>s`.'
27
27
 
28
28
  def_node_matcher :disallowed_stub, <<-PATTERN
@@ -25,7 +25,7 @@ module RuboCop
25
25
  # some_method
26
26
  # test.run
27
27
  # end
28
- class AroundBlock < Cop
28
+ class AroundBlock < Base
29
29
  MSG_NO_ARG = 'Test object should be passed to around block.'
30
30
  MSG_UNUSED_ARG = 'You should call `%<arg>s.call` '\
31
31
  'or `%<arg>s.run`.'
@@ -0,0 +1,74 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module RSpec
6
+ # @abstract parent class to RSpec cops
7
+ #
8
+ # The criteria for whether rubocop-rspec analyzes a certain ruby file
9
+ # is configured via `AllCops/RSpec`. For example, if you want to
10
+ # customize your project to scan all files within a `test/` directory
11
+ # then you could add this to your configuration:
12
+ #
13
+ # @example configuring analyzed paths
14
+ # # .rubocop.yml
15
+ # # AllCops:
16
+ # # RSpec:
17
+ # # Patterns:
18
+ # # - '_test.rb$'
19
+ # # - '(?:^|/)test/'
20
+ class Base < ::RuboCop::Cop::Base
21
+ include RuboCop::RSpec::Language
22
+ include RuboCop::RSpec::Language::NodePattern
23
+
24
+ DEFAULT_CONFIGURATION =
25
+ RuboCop::RSpec::CONFIG.fetch('AllCops').fetch('RSpec')
26
+
27
+ DEFAULT_PATTERN_RE = Regexp.union(
28
+ DEFAULT_CONFIGURATION.fetch('Patterns')
29
+ .map(&Regexp.public_method(:new))
30
+ )
31
+
32
+ # Invoke the original inherited hook so our cops are recognized
33
+ def self.inherited(subclass) # rubocop:disable Lint/MissingSuper
34
+ RuboCop::Cop::Base.inherited(subclass)
35
+ end
36
+
37
+ def relevant_file?(file)
38
+ relevant_rubocop_rspec_file?(file) && super
39
+ end
40
+
41
+ private
42
+
43
+ def relevant_rubocop_rspec_file?(file)
44
+ rspec_pattern.match?(file)
45
+ end
46
+
47
+ def rspec_pattern
48
+ if rspec_pattern_config?
49
+ Regexp.union(rspec_pattern_config.map(&Regexp.public_method(:new)))
50
+ else
51
+ DEFAULT_PATTERN_RE
52
+ end
53
+ end
54
+
55
+ def all_cops_config
56
+ config
57
+ .for_all_cops
58
+ end
59
+
60
+ def rspec_pattern_config?
61
+ return unless all_cops_config.key?('RSpec')
62
+
63
+ all_cops_config.fetch('RSpec').key?('Patterns')
64
+ end
65
+
66
+ def rspec_pattern_config
67
+ all_cops_config
68
+ .fetch('RSpec', DEFAULT_CONFIGURATION)
69
+ .fetch('Patterns')
70
+ end
71
+ end
72
+ end
73
+ end
74
+ end
@@ -19,7 +19,7 @@ module RuboCop
19
19
  # expect(foo).to be 1.0
20
20
  # expect(foo).to be(true)
21
21
  #
22
- class Be < Cop
22
+ class Be < Base
23
23
  MSG = 'Don\'t use `be` without an argument.'
24
24
 
25
25
  def_node_matcher :be_without_args, <<-PATTERN
@@ -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
@@ -35,7 +35,9 @@ module RuboCop
35
35
  # necessarily the same type as `b` since the `#==` operator can
36
36
  # coerce objects for comparison.
37
37
  #
38
- class BeEql < Cop
38
+ class BeEql < Base
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
@@ -23,7 +23,7 @@ module RuboCop
23
23
  # before(:each) { Widget.create }
24
24
  # after(:each) { Widget.delete_all }
25
25
  # end
26
- class BeforeAfterAll < Cop
26
+ class BeforeAfterAll < Base
27
27
  MSG = 'Beware of using `%<hook>s` as it may cause state to leak '\
28
28
  'between tests. If you are using `rspec-rails`, and '\
29
29
  '`use_transactional_fixtures` is enabled, then records created '\
@@ -23,7 +23,9 @@ module RuboCop
23
23
  # expect(page).to have_current_path("/callback")
24
24
  # expect(page).to have_current_path(/widgets/)
25
25
  #
26
- class CurrentPathExpectation < Cop
26
+ class CurrentPathExpectation < Base
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
@@ -40,7 +40,9 @@ module RuboCop
40
40
  # # ...
41
41
  # end
42
42
  # end
43
- class FeatureMethods < Cop
43
+ class FeatureMethods < Base
44
+ extend AutoCorrector
45
+
44
46
  MSG = 'Use `%<replacement>s` instead of `%<method>s`.'
45
47
 
46
48
  # https://git.io/v7Kwr
@@ -53,15 +55,18 @@ module RuboCop
53
55
  feature: :describe
54
56
  }.freeze
55
57
 
58
+ def_node_matcher :capybara_speak,
59
+ SelectorSet.new(MAP.keys).node_pattern_union
60
+
56
61
  def_node_matcher :spec?, <<-PATTERN
57
62
  (block
58
- (send #{RSPEC} {:describe :feature} ...)
63
+ (send #rspec? {:describe :feature} ...)
59
64
  ...)
60
65
  PATTERN
61
66
 
62
67
  def_node_matcher :feature_method, <<-PATTERN
63
68
  (block
64
- $(send #{RSPEC} ${#{MAP.keys.map(&:inspect).join(' ')}} ...)
69
+ $(send #rspec? $#capybara_speak ...)
65
70
  ...)
66
71
  PATTERN
67
72
 
@@ -71,18 +76,15 @@ module RuboCop
71
76
  feature_method(node) do |send_node, match|
72
77
  next if enabled?(match)
73
78
 
74
- add_offense(
75
- send_node,
76
- location: :selector,
77
- message: format(MSG, method: match, replacement: MAP[match])
78
- )
79
+ add_offense(send_node.loc.selector) do |corrector|
80
+ corrector.replace(send_node.loc.selector, MAP[match].to_s)
81
+ end
79
82
  end
80
83
  end
81
84
 
82
- def autocorrect(node)
83
- lambda do |corrector|
84
- corrector.replace(node.loc.selector, MAP[node.method_name].to_s)
85
- end
85
+ def message(range)
86
+ name = range.source.to_sym
87
+ format(MSG, method: name, replacement: MAP[name])
86
88
  end
87
89
 
88
90
  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 < Base
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