rubocop-rspec 2.16.0 → 2.18.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -4,75 +4,31 @@ module RuboCop
4
4
  module Cop
5
5
  module RSpec
6
6
  module Capybara
7
- # Checks for there is a more specific matcher offered by Capybara.
8
- #
9
- # @example
10
- #
11
- # # bad
12
- # expect(page).to have_selector('button')
13
- # expect(page).to have_no_selector('button.cls')
14
- # expect(page).to have_css('button')
15
- # expect(page).to have_no_css('a.cls', href: 'http://example.com')
16
- # expect(page).to have_css('table.cls')
17
- # expect(page).to have_css('select')
18
- # expect(page).to have_css('input', exact_text: 'foo')
19
- #
20
- # # good
21
- # expect(page).to have_button
22
- # expect(page).to have_no_button(class: 'cls')
23
- # expect(page).to have_button
24
- # expect(page).to have_no_link('foo', class: 'cls', href: 'http://example.com')
25
- # expect(page).to have_table(class: 'cls')
26
- # expect(page).to have_select
27
- # expect(page).to have_field('foo')
28
- #
29
- class SpecificMatcher < ::RuboCop::Cop::Base
30
- MSG = 'Prefer `%<good_matcher>s` over `%<bad_matcher>s`.'
31
- RESTRICT_ON_SEND = %i[have_selector have_no_selector have_css
32
- have_no_css].freeze
33
- SPECIFIC_MATCHER = {
34
- 'button' => 'button',
35
- 'a' => 'link',
36
- 'table' => 'table',
37
- 'select' => 'select',
38
- 'input' => 'field'
39
- }.freeze
40
-
41
- # @!method first_argument(node)
42
- def_node_matcher :first_argument, <<-PATTERN
43
- (send nil? _ (str $_) ... )
44
- PATTERN
45
-
46
- def on_send(node)
47
- first_argument(node) do |arg|
48
- next unless (matcher = specific_matcher(arg))
49
- next if CssSelector.multiple_selectors?(arg)
50
- next unless CapybaraHelp.specific_option?(node, arg, matcher)
51
- next unless CapybaraHelp.specific_pseudo_classes?(arg)
52
-
53
- add_offense(node, message: message(node, matcher))
54
- end
55
- end
56
-
57
- private
58
-
59
- def specific_matcher(arg)
60
- splitted_arg = arg[/^\w+/, 0]
61
- SPECIFIC_MATCHER[splitted_arg]
62
- end
63
-
64
- def message(node, matcher)
65
- format(MSG,
66
- good_matcher: good_matcher(node, matcher),
67
- bad_matcher: node.method_name)
68
- end
69
-
70
- def good_matcher(node, matcher)
71
- node.method_name
72
- .to_s
73
- .gsub(/selector|css/, matcher.to_s)
74
- end
75
- end
7
+ # @!parse
8
+ # # Checks for there is a more specific matcher offered by Capybara.
9
+ # #
10
+ # # @example
11
+ # #
12
+ # # # bad
13
+ # # expect(page).to have_selector('button')
14
+ # # expect(page).to have_no_selector('button.cls')
15
+ # # expect(page).to have_css('button')
16
+ # # expect(page).to have_no_css('a.cls', href: 'http://example.com')
17
+ # # expect(page).to have_css('table.cls')
18
+ # # expect(page).to have_css('select')
19
+ # # expect(page).to have_css('input', exact_text: 'foo')
20
+ # #
21
+ # # # good
22
+ # # expect(page).to have_button
23
+ # # expect(page).to have_no_button(class: 'cls')
24
+ # # expect(page).to have_button
25
+ # # expect(page).to have_no_link('foo', class: 'cls', href: 'http://example.com')
26
+ # # expect(page).to have_table(class: 'cls')
27
+ # # expect(page).to have_select
28
+ # # expect(page).to have_field('foo')
29
+ # #
30
+ # class SpecificMatcher < ::RuboCop::Cop::Base; end
31
+ SpecificMatcher = ::RuboCop::Cop::Capybara::SpecificMatcher
76
32
  end
77
33
  end
78
34
  end
@@ -4,69 +4,32 @@ module RuboCop
4
4
  module Cop
5
5
  module RSpec
6
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
- # Read more in
16
- # https://www.rubydoc.info/gems/capybara/Capybara%2FNode%2FFinders:all[the documentation].
17
- #
18
- # @example
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 < ::RuboCop::Cop::Base
30
- MSG_FALSE = 'Use `:all` or `:hidden` instead of `false`.'
31
- MSG_TRUE = 'Use `:visible` instead of `true`.'
32
- CAPYBARA_MATCHER_METHODS = %w[
33
- button
34
- checked_field
35
- css
36
- field
37
- link
38
- select
39
- selector
40
- table
41
- unchecked_field
42
- xpath
43
- ].flat_map do |element|
44
- ["have_#{element}".to_sym, "have_no_#{element}".to_sym]
45
- end
46
-
47
- RESTRICT_ON_SEND = CAPYBARA_MATCHER_METHODS
48
-
49
- # @!method visible_true?(node)
50
- def_node_matcher :visible_true?, <<~PATTERN
51
- (send nil? #capybara_matcher? ... (hash <$(pair (sym :visible) true) ...>))
52
- PATTERN
53
-
54
- # @!method visible_false?(node)
55
- def_node_matcher :visible_false?, <<~PATTERN
56
- (send nil? #capybara_matcher? ... (hash <$(pair (sym :visible) false) ...>))
57
- PATTERN
58
-
59
- def on_send(node)
60
- visible_false?(node) { |arg| add_offense(arg, message: MSG_FALSE) }
61
- visible_true?(node) { |arg| add_offense(arg, message: MSG_TRUE) }
62
- end
63
-
64
- private
65
-
66
- def capybara_matcher?(method_name)
67
- CAPYBARA_MATCHER_METHODS.include? method_name
68
- end
69
- end
7
+ # @!parse
8
+ # # Checks for boolean visibility in Capybara finders.
9
+ # #
10
+ # # Capybara lets you find elements that match a certain visibility
11
+ # # using the `:visible` option. `:visible` accepts both boolean and
12
+ # # symbols as values, however using booleans can have unwanted
13
+ # # effects. `visible: false` does not find just invisible elements,
14
+ # # but both visible and invisible elements. For expressiveness and
15
+ # # clarity, use one of the # symbol values, `:all`, `:hidden` or
16
+ # # `:visible`.
17
+ # # Read more in
18
+ # # https://www.rubydoc.info/gems/capybara/Capybara%2FNode%2FFinders:all[the documentation].
19
+ # #
20
+ # # @example
21
+ # # # bad
22
+ # # expect(page).to have_selector('.foo', visible: false)
23
+ # # expect(page).to have_css('.foo', visible: true)
24
+ # # expect(page).to have_link('my link', visible: false)
25
+ # #
26
+ # # # good
27
+ # # expect(page).to have_selector('.foo', visible: :visible)
28
+ # # expect(page).to have_css('.foo', visible: :all)
29
+ # # expect(page).to have_link('my link', visible: :hidden)
30
+ # #
31
+ # class VisibilityMatcher < ::RuboCop::Cop::Base; end
32
+ VisibilityMatcher = ::RuboCop::Cop::Capybara::VisibilityMatcher
70
33
  end
71
34
  end
72
35
  end
@@ -31,7 +31,11 @@ module RuboCop
31
31
 
32
32
  # @!method context_method(node)
33
33
  def_node_matcher :context_method, <<-PATTERN
34
- (block (send #rspec? :context $(str #method_name?) ...) ...)
34
+ (block
35
+ (send #rspec? :context
36
+ ${(str #method_name?) (dstr (str #method_name?) ...)}
37
+ ...)
38
+ ...)
35
39
  PATTERN
36
40
 
37
41
  def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler
@@ -10,7 +10,6 @@ module RuboCop
10
10
  # include `if`, `unless`, `for`, `before`, `after`, or `during`.
11
11
  # They may consist of multiple words if desired.
12
12
  #
13
- # @see https://rspec.rubystyle.guide/#context-descriptions
14
13
  # @see http://www.betterspecs.org/#contexts
15
14
  #
16
15
  # @example `Prefixes` configuration
@@ -11,7 +11,7 @@ module RuboCop
11
11
  def_node_matcher :skipped_in_metadata?, <<-PATTERN
12
12
  {
13
13
  (send _ _ <#skip_or_pending? ...>)
14
- (send _ _ ... (hash <(pair #skip_or_pending? { true str }) ...>))
14
+ (send _ _ ... (hash <(pair #skip_or_pending? { true str dstr }) ...>))
15
15
  }
16
16
  PATTERN
17
17
 
@@ -87,35 +87,33 @@ module RuboCop
87
87
  (send #rspec? {#ExampleGroups.all #Examples.all} ... {<(sym :skip) ...> (hash <(pair (sym :skip) true) ...>)})
88
88
  PATTERN
89
89
 
90
+ # @!method without_reason?(node)
91
+ def_node_matcher :without_reason?, <<~PATTERN
92
+ (send nil? ${:pending :skip})
93
+ PATTERN
94
+
90
95
  def on_send(node)
91
96
  if pending_without_reason?(node)
92
97
  add_offense(node, message: 'Give the reason for pending.')
93
98
  elsif skipped_without_reason?(node)
94
99
  add_offense(node, message: 'Give the reason for skip.')
100
+ elsif without_reason?(node) && example?(node.parent)
101
+ add_offense(node,
102
+ message: "Give the reason for #{node.method_name}.")
95
103
  end
96
104
  end
97
105
 
98
106
  private
99
107
 
100
- def pending_by_pending_step_without_reason?(node)
101
- node.method?(:pending) && node.first_argument.nil?
102
- end
103
-
104
108
  def pending_without_reason?(node)
105
109
  pending_by_example_method?(node.block_node) ||
106
- pending_by_metadata_without_reason?(node) ||
107
- pending_by_pending_step_without_reason?(node)
108
- end
109
-
110
- def skipped_by_skip_step_without_reason?(node)
111
- node.method?(:skip) && node.first_argument.nil?
110
+ pending_by_metadata_without_reason?(node)
112
111
  end
113
112
 
114
113
  def skipped_without_reason?(node)
115
114
  skipped_by_example_group_method?(node.block_node) ||
116
115
  skipped_by_example_method?(node.block_node) ||
117
- skipped_by_metadata_without_reason?(node) ||
118
- skipped_by_skip_step_without_reason?(node)
116
+ skipped_by_metadata_without_reason?(node)
119
117
  end
120
118
  end
121
119
  end
@@ -118,7 +118,7 @@ module RuboCop
118
118
  end
119
119
 
120
120
  # A helper for `explicit` style
121
- module ExplicitHelper
121
+ module ExplicitHelper # rubocop:disable Metrics/ModuleLength
122
122
  include RuboCop::RSpec::Language
123
123
  extend NodePattern::Macros
124
124
 
@@ -149,12 +149,35 @@ module RuboCop
149
149
  return if part_of_ignored_node?(node)
150
150
 
151
151
  predicate_matcher?(node) do |actual, matcher|
152
+ next unless replaceable_matcher?(matcher)
153
+
152
154
  add_offense(node, message: message_explicit(matcher)) do |corrector|
155
+ next if uncorrectable_matcher?(node, matcher)
156
+
153
157
  corrector_explicit(corrector, node, actual, matcher, matcher)
154
158
  end
155
159
  end
156
160
  end
157
161
 
162
+ def replaceable_matcher?(matcher)
163
+ case matcher.method_name.to_s
164
+ when 'include'
165
+ matcher.arguments.one?
166
+ else
167
+ true
168
+ end
169
+ end
170
+
171
+ def uncorrectable_matcher?(node, matcher)
172
+ heredoc_argument?(matcher) && !same_line?(node, matcher)
173
+ end
174
+
175
+ def heredoc_argument?(matcher)
176
+ matcher.arguments.select do |arg|
177
+ %i[str dstr xstr].include?(arg.type)
178
+ end.any?(&:heredoc?)
179
+ end
180
+
158
181
  # @!method predicate_matcher?(node)
159
182
  def_node_matcher :predicate_matcher?, <<-PATTERN
160
183
  (send
@@ -179,7 +202,8 @@ module RuboCop
179
202
 
180
203
  return false if allowed_explicit_matchers.include?(name)
181
204
 
182
- name.start_with?('be_', 'have_') && !name.end_with?('?')
205
+ name.start_with?('be_', 'have_') && !name.end_with?('?') ||
206
+ %w[include respond_to].include?(name)
183
207
  end
184
208
 
185
209
  def message_explicit(matcher)
@@ -270,6 +294,17 @@ module RuboCop
270
294
  # # good - the above code is rewritten to it by this cop
271
295
  # expect(foo.something?).to be(true)
272
296
  #
297
+ # # bad - no autocorrect
298
+ # expect(foo)
299
+ # .to be_something(<<~TEXT)
300
+ # bar
301
+ # TEXT
302
+ #
303
+ # # good
304
+ # expect(foo.something?(<<~TEXT)).to be(true)
305
+ # bar
306
+ # TEXT
307
+ #
273
308
  # @example Strict: false, EnforcedStyle: explicit
274
309
  # # bad
275
310
  # expect(foo).to be_something
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module RSpec
6
+ module Rails
7
+ # Check if using Minitest matchers.
8
+ #
9
+ # @example
10
+ # # bad
11
+ # assert_equal(a, b)
12
+ # assert_equal a, b, "must be equal"
13
+ # refute_equal(a, b)
14
+ #
15
+ # # good
16
+ # expect(a).to eq(b)
17
+ # expect(a).to(eq(b), "must be equal")
18
+ # expect(a).not_to eq(b)
19
+ #
20
+ class MinitestAssertions < Base
21
+ extend AutoCorrector
22
+
23
+ MSG = 'Use `%<prefer>s`.'
24
+ RESTRICT_ON_SEND = %i[assert_equal refute_equal].freeze
25
+
26
+ # @!method minitest_assertion(node)
27
+ def_node_matcher :minitest_assertion, <<-PATTERN
28
+ (send nil? {:assert_equal :refute_equal} $_ $_ $_?)
29
+ PATTERN
30
+
31
+ def on_send(node)
32
+ minitest_assertion(node) do |expected, actual, failure_message|
33
+ prefer = replacement(node, expected, actual,
34
+ failure_message.first)
35
+ add_offense(node, message: message(prefer)) do |corrector|
36
+ corrector.replace(node, prefer)
37
+ end
38
+ end
39
+ end
40
+
41
+ private
42
+
43
+ def replacement(node, expected, actual, failure_message)
44
+ runner = node.method?(:assert_equal) ? 'to' : 'not_to'
45
+ if failure_message.nil?
46
+ "expect(#{expected.source}).#{runner} eq(#{actual.source})"
47
+ else
48
+ "expect(#{expected.source}).#{runner}(eq(#{actual.source}), " \
49
+ "#{failure_message.source})"
50
+ end
51
+ end
52
+
53
+ def message(prefer)
54
+ format(MSG, prefer: prefer)
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
@@ -91,7 +91,7 @@ module RuboCop
91
91
  # @param node [RuboCop::AST::Node]
92
92
  # @yield [RuboCop::AST::Node] matcher
93
93
  def_node_matcher :matcher_with_return_block, <<~PATTERN
94
- (block #message_expectation? args _) # receive(:foo) { 'bar' }
94
+ (block #message_expectation? (args) _) # receive(:foo) { 'bar' }
95
95
  PATTERN
96
96
 
97
97
  # @!method matcher_with_hash(node)
@@ -12,7 +12,6 @@ module RuboCop
12
12
  #
13
13
  # @see https://robots.thoughtbot.com/don-t-stub-the-system-under-test
14
14
  # @see https://penelope.zone/2015/12/27/introducing-rspec-smells-and-where-to-find-them.html#smell-1-stubjec
15
- # @see https://github.com/rubocop-hq/rspec-style-guide#dont-stub-subject
16
15
  #
17
16
  # @example
18
17
  # # bad
@@ -2,6 +2,7 @@
2
2
 
3
3
  require_relative 'rspec/capybara/current_path_expectation'
4
4
  require_relative 'rspec/capybara/feature_methods'
5
+ require_relative 'rspec/capybara/match_style'
5
6
  require_relative 'rspec/capybara/negation_matcher'
6
7
  require_relative 'rspec/capybara/specific_actions'
7
8
  require_relative 'rspec/capybara/specific_finders'
@@ -23,6 +24,7 @@ rescue LoadError
23
24
  # Rails/HttpStatus cannot be loaded if rack/utils is unavailable.
24
25
  end
25
26
  require_relative 'rspec/rails/inferred_spec_type'
27
+ require_relative 'rspec/rails/minitest_assertions'
26
28
 
27
29
  require_relative 'rspec/align_left_let_brace'
28
30
  require_relative 'rspec/align_right_let_brace'
@@ -8,6 +8,15 @@ module RuboCop
8
8
  class ConfigFormatter
9
9
  EXTENSION_ROOT_DEPARTMENT = %r{^(RSpec/)}.freeze
10
10
  SUBDEPARTMENTS = %(RSpec/Capybara RSpec/FactoryBot RSpec/Rails)
11
+ EXTRACTED_COPS = %(
12
+ RSpec/Capybara/CurrentPathExpectation
13
+ RSpec/Capybara/MatchStyle
14
+ RSpec/Capybara/NegationMatcher
15
+ RSpec/Capybara/SpecificActions
16
+ RSpec/Capybara/SpecificFinders
17
+ RSpec/Capybara/SpecificMatcher
18
+ RSpec/Capybara/VisibilityMatcher
19
+ )
11
20
  AMENDMENTS = %(Metrics/BlockLength)
12
21
  COP_DOC_BASE_URL = 'https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/'
13
22
 
@@ -29,6 +38,7 @@ module RuboCop
29
38
  def unified_config
30
39
  cops.each_with_object(config.dup) do |cop, unified|
31
40
  next if SUBDEPARTMENTS.include?(cop) || AMENDMENTS.include?(cop)
41
+ next if EXTRACTED_COPS.include?(cop)
32
42
 
33
43
  replace_nil(unified[cop])
34
44
  unified[cop].merge!(descriptions.fetch(cop))
@@ -4,7 +4,7 @@ module RuboCop
4
4
  module RSpec
5
5
  # Version information for the RSpec RuboCop plugin.
6
6
  module Version
7
- STRING = '2.16.0'
7
+ STRING = '2.18.1'
8
8
  end
9
9
  end
10
10
  end
data/lib/rubocop-rspec.rb CHANGED
@@ -4,6 +4,7 @@ require 'pathname'
4
4
  require 'yaml'
5
5
 
6
6
  require 'rubocop'
7
+ require 'rubocop-capybara'
7
8
 
8
9
  require_relative 'rubocop/rspec'
9
10
  require_relative 'rubocop/rspec/inject'
@@ -17,8 +18,6 @@ require_relative 'rubocop/rspec/language'
17
18
 
18
19
  require_relative 'rubocop/rspec/factory_bot/language'
19
20
 
20
- require_relative 'rubocop/cop/rspec/mixin/capybara_help'
21
- require_relative 'rubocop/cop/rspec/mixin/css_selector'
22
21
  require_relative 'rubocop/cop/rspec/mixin/final_end_location'
23
22
  require_relative 'rubocop/cop/rspec/mixin/inside_example_group'
24
23
  require_relative 'rubocop/cop/rspec/mixin/metadata'
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rubocop-rspec
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.16.0
4
+ version: 2.18.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - John Backus
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2022-12-13 00:00:00.000000000 Z
13
+ date: 2023-01-19 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: rubocop
@@ -26,6 +26,20 @@ dependencies:
26
26
  - - "~>"
27
27
  - !ruby/object:Gem::Version
28
28
  version: '1.33'
29
+ - !ruby/object:Gem::Dependency
30
+ name: rubocop-capybara
31
+ requirement: !ruby/object:Gem::Requirement
32
+ requirements:
33
+ - - "~>"
34
+ - !ruby/object:Gem::Version
35
+ version: '2.17'
36
+ type: :runtime
37
+ prerelease: false
38
+ version_requirements: !ruby/object:Gem::Requirement
39
+ requirements:
40
+ - - "~>"
41
+ - !ruby/object:Gem::Version
42
+ version: '2.17'
29
43
  description: |2
30
44
  Code style checking for RSpec files.
31
45
  A plugin for the RuboCop code style enforcing & linting tool.
@@ -58,6 +72,7 @@ files:
58
72
  - lib/rubocop/cop/rspec/before_after_all.rb
59
73
  - lib/rubocop/cop/rspec/capybara/current_path_expectation.rb
60
74
  - lib/rubocop/cop/rspec/capybara/feature_methods.rb
75
+ - lib/rubocop/cop/rspec/capybara/match_style.rb
61
76
  - lib/rubocop/cop/rspec/capybara/negation_matcher.rb
62
77
  - lib/rubocop/cop/rspec/capybara/specific_actions.rb
63
78
  - lib/rubocop/cop/rspec/capybara/specific_finders.rb
@@ -115,9 +130,7 @@ files:
115
130
  - lib/rubocop/cop/rspec/message_expectation.rb
116
131
  - lib/rubocop/cop/rspec/message_spies.rb
117
132
  - lib/rubocop/cop/rspec/missing_example_group_argument.rb
118
- - lib/rubocop/cop/rspec/mixin/capybara_help.rb
119
133
  - lib/rubocop/cop/rspec/mixin/comments_help.rb
120
- - lib/rubocop/cop/rspec/mixin/css_selector.rb
121
134
  - lib/rubocop/cop/rspec/mixin/empty_line_separation.rb
122
135
  - lib/rubocop/cop/rspec/mixin/final_end_location.rb
123
136
  - lib/rubocop/cop/rspec/mixin/inside_example_group.rb
@@ -142,6 +155,7 @@ files:
142
155
  - lib/rubocop/cop/rspec/rails/have_http_status.rb
143
156
  - lib/rubocop/cop/rspec/rails/http_status.rb
144
157
  - lib/rubocop/cop/rspec/rails/inferred_spec_type.rb
158
+ - lib/rubocop/cop/rspec/rails/minitest_assertions.rb
145
159
  - lib/rubocop/cop/rspec/receive_counts.rb
146
160
  - lib/rubocop/cop/rspec/receive_never.rb
147
161
  - lib/rubocop/cop/rspec/repeated_description.rb
@@ -207,7 +221,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
207
221
  - !ruby/object:Gem::Version
208
222
  version: '0'
209
223
  requirements: []
210
- rubygems_version: 3.1.6
224
+ rubygems_version: 3.3.7
211
225
  signing_key:
212
226
  specification_version: 4
213
227
  summary: Code style checking for RSpec files
@@ -1,80 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module RuboCop
4
- module Cop
5
- module RSpec
6
- # Help methods for capybara.
7
- module CapybaraHelp
8
- module_function
9
-
10
- # @param node [RuboCop::AST::SendNode]
11
- # @param locator [String]
12
- # @param element [String]
13
- # @return [Boolean]
14
- def specific_option?(node, locator, element)
15
- attrs = CssSelector.attributes(locator).keys
16
- return false unless replaceable_element?(node, element, attrs)
17
-
18
- attrs.all? do |attr|
19
- CssSelector.specific_options?(element, attr)
20
- end
21
- end
22
-
23
- # @param locator [String]
24
- # @return [Boolean]
25
- def specific_pseudo_classes?(locator)
26
- CssSelector.pseudo_classes(locator).all? do |pseudo_class|
27
- replaceable_pseudo_class?(pseudo_class, locator)
28
- end
29
- end
30
-
31
- # @param pseudo_class [String]
32
- # @param locator [String]
33
- # @return [Boolean]
34
- def replaceable_pseudo_class?(pseudo_class, locator)
35
- return false unless CssSelector.specific_pesudo_classes?(pseudo_class)
36
-
37
- case pseudo_class
38
- when 'not()' then replaceable_pseudo_class_not?(locator)
39
- else true
40
- end
41
- end
42
-
43
- # @param locator [String]
44
- # @return [Boolean]
45
- def replaceable_pseudo_class_not?(locator)
46
- locator.scan(/not\(.*?\)/).all? do |negation|
47
- CssSelector.attributes(negation).values.all? do |v|
48
- v.is_a?(TrueClass) || v.is_a?(FalseClass)
49
- end
50
- end
51
- end
52
-
53
- # @param node [RuboCop::AST::SendNode]
54
- # @param element [String]
55
- # @param attrs [Array<String>]
56
- # @return [Boolean]
57
- def replaceable_element?(node, element, attrs)
58
- case element
59
- when 'link' then replaceable_to_link?(node, attrs)
60
- else true
61
- end
62
- end
63
-
64
- # @param node [RuboCop::AST::SendNode]
65
- # @param attrs [Array<String>]
66
- # @return [Boolean]
67
- def replaceable_to_link?(node, attrs)
68
- include_option?(node, :href) || attrs.include?('href')
69
- end
70
-
71
- # @param node [RuboCop::AST::SendNode]
72
- # @param option [Symbol]
73
- # @return [Boolean]
74
- def include_option?(node, option)
75
- node.each_descendant(:sym).find { |opt| opt.value == option }
76
- end
77
- end
78
- end
79
- end
80
- end