rubocop-rspec 2.16.0 → 2.18.1

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