rubocop-capybara 2.17.0 → 2.17.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +8 -2
- data/README.md +1 -1
- data/config/default.yml +3 -1
- data/lib/rubocop/capybara/version.rb +1 -1
- data/lib/rubocop/cop/capybara/current_path_expectation.rb +18 -8
- data/lib/rubocop/cop/capybara/mixin/capybara_help.rb +107 -55
- data/lib/rubocop/cop/capybara/mixin/css_selector.rb +91 -125
- data/lib/rubocop/cop/capybara/specific_actions.rb +13 -3
- data/lib/rubocop/cop/capybara/specific_finders.rb +40 -5
- data/lib/rubocop/cop/capybara/specific_matcher.rb +13 -2
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8f842364dafbf74a569f7e4d40eeee32b49bf3b9983d7ada2f376fb919481bdf
|
4
|
+
data.tar.gz: 075717405e4165171110bbdd29863873b4b94822baef45cb3946d8055da497bb
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 70e7fe7215dda9d8ae088bc0df07e50cf3d2f1c0645afe8797d7ce4a03b080a710b52da0403b37ce0ec6dc1a428d999e0342ea53d39d406c68f894059d9d7f6c
|
7
|
+
data.tar.gz: 7d17ff840e4f4ac78b079bbc9a567c10b1fbf2419f748b932bd67a2bdfe43e2a825dd06735974daff51dc35f323824803010e4526adedef9dc0812f77a8fe5f7
|
data/CHANGELOG.md
CHANGED
@@ -2,11 +2,17 @@
|
|
2
2
|
|
3
3
|
## Edge (Unreleased)
|
4
4
|
|
5
|
-
## 2.17.
|
5
|
+
## 2.17.1 (2023-02-13)
|
6
|
+
|
7
|
+
- Fix an incorrect autocorrect for `Capybara/CurrentPathExpectation`. ([@ydah])
|
8
|
+
- Fix a false negative for `Capybara/CurrentPathExpectation` when using `match`. ([@ydah])
|
9
|
+
- Fix a false positive and incorrect autocorrect for `Capybara/SpecificActions`, `Capybara/SpecificFinders` and `Capybara/SpecificMatcher`. ([@ydah])
|
10
|
+
|
11
|
+
## 2.17.0 (2022-12-29)
|
6
12
|
|
7
13
|
- Extracted from `rubocop-rspec` into a separate repository for easier use with Minitest/Cucumber. ([@pirj])
|
8
14
|
|
9
|
-
## Previously (see rubocop-rspec's changelist for details)
|
15
|
+
## Previously (see [rubocop-rspec's changelist](https://github.com/rubocop/rubocop-rspec/blob/9558719/CHANGELOG.md) for details)
|
10
16
|
|
11
17
|
- Fix a false positive for `Capybara/SpecificMatcher` when `have_css("a")` without attribute. ([@ydah])
|
12
18
|
- Add new `Capybara/NegationMatcher` cop. ([@ydah])
|
data/README.md
CHANGED
@@ -4,7 +4,7 @@
|
|
4
4
|
[](https://rubygems.org/gems/rubocop-capybara)
|
5
5
|

|
6
6
|
|
7
|
-
Capybara-specific analysis for your projects, as an extension to
|
7
|
+
[Capybara](https://teamcapybara.github.io/capybara)-specific analysis for your projects, as an extension to
|
8
8
|
[RuboCop](https://github.com/rubocop/rubocop).
|
9
9
|
|
10
10
|
## Installation
|
data/config/default.yml
CHANGED
@@ -6,6 +6,8 @@ Capybara:
|
|
6
6
|
- "**/*_spec.rb"
|
7
7
|
- "**/spec/**/*"
|
8
8
|
- "**/test/**/*"
|
9
|
+
- "**/*_steps.rb"
|
10
|
+
- "**/features/step_definitions/**/*"
|
9
11
|
|
10
12
|
Capybara/CurrentPathExpectation:
|
11
13
|
Description: Checks that no expectations are set on Capybara's `current_path`.
|
@@ -17,7 +19,7 @@ Capybara/CurrentPathExpectation:
|
|
17
19
|
Capybara/MatchStyle:
|
18
20
|
Description: Checks for usage of deprecated style methods.
|
19
21
|
Enabled: pending
|
20
|
-
VersionAdded:
|
22
|
+
VersionAdded: '2.17'
|
21
23
|
Reference: https://www.rubydoc.info/gems/rubocop-capybara/RuboCop/Cop/Capybara/MatchStyle
|
22
24
|
|
23
25
|
Capybara/NegationMatcher:
|
@@ -50,11 +50,11 @@ module RuboCop
|
|
50
50
|
${(send nil? :eq ...) (send nil? :match (regexp ...))})
|
51
51
|
PATTERN
|
52
52
|
|
53
|
-
# @!method
|
54
|
-
def_node_matcher :
|
53
|
+
# @!method regexp_node_matcher(node)
|
54
|
+
def_node_matcher :regexp_node_matcher, <<-PATTERN
|
55
55
|
(send
|
56
56
|
#expectation_set_on_current_path ${:to :to_not :not_to}
|
57
|
-
$(send nil? :match
|
57
|
+
$(send nil? :match ${str dstr xstr}))
|
58
58
|
PATTERN
|
59
59
|
|
60
60
|
def self.autocorrect_incompatible_with
|
@@ -78,9 +78,9 @@ module RuboCop
|
|
78
78
|
rewrite_expectation(corrector, node, to_sym, matcher_node)
|
79
79
|
end
|
80
80
|
|
81
|
-
|
81
|
+
regexp_node_matcher(node.parent) do |to_sym, matcher_node, regexp|
|
82
82
|
rewrite_expectation(corrector, node, to_sym, matcher_node)
|
83
|
-
|
83
|
+
convert_regexp_node_to_literal(corrector, matcher_node, regexp)
|
84
84
|
end
|
85
85
|
end
|
86
86
|
|
@@ -97,12 +97,20 @@ module RuboCop
|
|
97
97
|
add_ignore_query_options(corrector, node)
|
98
98
|
end
|
99
99
|
|
100
|
-
def
|
100
|
+
def convert_regexp_node_to_literal(corrector, matcher_node, regexp_node)
|
101
101
|
str_node = matcher_node.first_argument
|
102
|
-
regexp_expr =
|
102
|
+
regexp_expr = regexp_node_to_regexp_expr(regexp_node)
|
103
103
|
corrector.replace(str_node, regexp_expr)
|
104
104
|
end
|
105
105
|
|
106
|
+
def regexp_node_to_regexp_expr(regexp_node)
|
107
|
+
if regexp_node.xstr_type?
|
108
|
+
"/\#{`#{regexp_node.value.value}`}/"
|
109
|
+
else
|
110
|
+
Regexp.new(regexp_node.value).inspect
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
106
114
|
# `have_current_path` with no options will include the querystring
|
107
115
|
# while `page.current_path` does not.
|
108
116
|
# This ensures the option `ignore_query: true` is added
|
@@ -110,7 +118,9 @@ module RuboCop
|
|
110
118
|
def add_ignore_query_options(corrector, node)
|
111
119
|
expectation_node = node.parent.last_argument
|
112
120
|
expectation_last_child = expectation_node.children.last
|
113
|
-
return if %i[
|
121
|
+
return if %i[
|
122
|
+
regexp str dstr xstr
|
123
|
+
].include?(expectation_last_child.type)
|
114
124
|
|
115
125
|
corrector.insert_after(
|
116
126
|
expectation_last_child,
|
@@ -2,76 +2,128 @@
|
|
2
2
|
|
3
3
|
module RuboCop
|
4
4
|
module Cop
|
5
|
-
|
6
|
-
|
7
|
-
|
5
|
+
module Capybara
|
6
|
+
# Help methods for capybara.
|
7
|
+
module CapybaraHelp
|
8
|
+
COMMON_OPTIONS = %w[
|
9
|
+
id class style
|
10
|
+
].freeze
|
11
|
+
SPECIFIC_OPTIONS = {
|
12
|
+
'button' => (
|
13
|
+
COMMON_OPTIONS + %w[disabled name value title type]
|
14
|
+
).freeze,
|
15
|
+
'link' => (
|
16
|
+
COMMON_OPTIONS + %w[href alt title download]
|
17
|
+
).freeze,
|
18
|
+
'table' => (
|
19
|
+
COMMON_OPTIONS + %w[cols rows]
|
20
|
+
).freeze,
|
21
|
+
'select' => (
|
22
|
+
COMMON_OPTIONS + %w[
|
23
|
+
disabled name placeholder
|
24
|
+
selected multiple
|
25
|
+
]
|
26
|
+
).freeze,
|
27
|
+
'field' => (
|
28
|
+
COMMON_OPTIONS + %w[
|
29
|
+
checked disabled name placeholder
|
30
|
+
readonly type multiple
|
31
|
+
]
|
32
|
+
).freeze
|
33
|
+
}.freeze
|
34
|
+
SPECIFIC_PSEUDO_CLASSES = %w[
|
35
|
+
not() disabled enabled checked unchecked
|
36
|
+
].freeze
|
8
37
|
|
9
|
-
|
10
|
-
# @param locator [String]
|
11
|
-
# @param element [String]
|
12
|
-
# @return [Boolean]
|
13
|
-
def specific_option?(node, locator, element)
|
14
|
-
attrs = CssSelector.attributes(locator).keys
|
15
|
-
return false unless replaceable_element?(node, element, attrs)
|
38
|
+
module_function
|
16
39
|
|
17
|
-
|
18
|
-
|
40
|
+
# @param node [RuboCop::AST::SendNode]
|
41
|
+
# @param locator [String]
|
42
|
+
# @param element [String]
|
43
|
+
# @return [Boolean]
|
44
|
+
def replaceable_option?(node, locator, element)
|
45
|
+
attrs = CssSelector.attributes(locator).keys
|
46
|
+
return false unless replaceable_element?(node, element, attrs)
|
47
|
+
|
48
|
+
attrs.all? do |attr|
|
49
|
+
SPECIFIC_OPTIONS.fetch(element, []).include?(attr)
|
50
|
+
end
|
19
51
|
end
|
20
|
-
end
|
21
52
|
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
53
|
+
# @param selector [String]
|
54
|
+
# @return [Boolean]
|
55
|
+
# @example
|
56
|
+
# common_attributes?('a[focused]') # => true
|
57
|
+
# common_attributes?('button[focused][visible]') # => true
|
58
|
+
# common_attributes?('table[id=some-id]') # => true
|
59
|
+
# common_attributes?('h1[invalid]') # => false
|
60
|
+
def common_attributes?(selector)
|
61
|
+
CssSelector.attributes(selector).keys.difference(COMMON_OPTIONS).none?
|
27
62
|
end
|
28
|
-
end
|
29
63
|
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
64
|
+
# @param attrs [Array<String>]
|
65
|
+
# @return [Boolean]
|
66
|
+
# @example
|
67
|
+
# replaceable_attributes?('table[id=some-id]') # => true
|
68
|
+
# replaceable_attributes?('a[focused]') # => false
|
69
|
+
def replaceable_attributes?(attrs)
|
70
|
+
attrs.values.none?(&:nil?)
|
71
|
+
end
|
35
72
|
|
36
|
-
|
37
|
-
|
38
|
-
|
73
|
+
# @param locator [String]
|
74
|
+
# @return [Boolean]
|
75
|
+
def replaceable_pseudo_classes?(locator)
|
76
|
+
CssSelector.pseudo_classes(locator).all? do |pseudo_class|
|
77
|
+
replaceable_pseudo_class?(pseudo_class, locator)
|
78
|
+
end
|
39
79
|
end
|
40
|
-
end
|
41
80
|
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
locator
|
46
|
-
|
47
|
-
|
81
|
+
# @param pseudo_class [String]
|
82
|
+
# @param locator [String]
|
83
|
+
# @return [Boolean]
|
84
|
+
def replaceable_pseudo_class?(pseudo_class, locator)
|
85
|
+
return false unless SPECIFIC_PSEUDO_CLASSES.include?(pseudo_class)
|
86
|
+
|
87
|
+
case pseudo_class
|
88
|
+
when 'not()' then replaceable_pseudo_class_not?(locator)
|
89
|
+
else true
|
48
90
|
end
|
49
91
|
end
|
50
|
-
end
|
51
92
|
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
93
|
+
# @param locator [String]
|
94
|
+
# @return [Boolean]
|
95
|
+
def replaceable_pseudo_class_not?(locator)
|
96
|
+
locator.scan(/not\(.*?\)/).all? do |negation|
|
97
|
+
CssSelector.attributes(negation).values.all? do |v|
|
98
|
+
v.is_a?(TrueClass) || v.is_a?(FalseClass)
|
99
|
+
end
|
100
|
+
end
|
60
101
|
end
|
61
|
-
end
|
62
102
|
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
103
|
+
# @param node [RuboCop::AST::SendNode]
|
104
|
+
# @param element [String]
|
105
|
+
# @param attrs [Array<String>]
|
106
|
+
# @return [Boolean]
|
107
|
+
def replaceable_element?(node, element, attrs)
|
108
|
+
case element
|
109
|
+
when 'link' then replaceable_to_link?(node, attrs)
|
110
|
+
else true
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
# @param node [RuboCop::AST::SendNode]
|
115
|
+
# @param attrs [Array<String>]
|
116
|
+
# @return [Boolean]
|
117
|
+
def replaceable_to_link?(node, attrs)
|
118
|
+
include_option?(node, :href) || attrs.include?('href')
|
119
|
+
end
|
69
120
|
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
121
|
+
# @param node [RuboCop::AST::SendNode]
|
122
|
+
# @param option [Symbol]
|
123
|
+
# @return [Boolean]
|
124
|
+
def include_option?(node, option)
|
125
|
+
node.each_descendant(:sym).find { |opt| opt.value == option }
|
126
|
+
end
|
75
127
|
end
|
76
128
|
end
|
77
129
|
end
|
@@ -2,141 +2,107 @@
|
|
2
2
|
|
3
3
|
module RuboCop
|
4
4
|
module Cop
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
id class style visible obscured exact exact_text normalize_ws match
|
10
|
-
wait filter_set focused
|
11
|
-
].freeze
|
12
|
-
SPECIFIC_OPTIONS = {
|
13
|
-
'button' => (
|
14
|
-
COMMON_OPTIONS + %w[disabled name value title type]
|
15
|
-
).freeze,
|
16
|
-
'link' => (
|
17
|
-
COMMON_OPTIONS + %w[href alt title download]
|
18
|
-
).freeze,
|
19
|
-
'table' => (
|
20
|
-
COMMON_OPTIONS + %w[
|
21
|
-
caption with_cols cols with_rows rows
|
22
|
-
]
|
23
|
-
).freeze,
|
24
|
-
'select' => (
|
25
|
-
COMMON_OPTIONS + %w[
|
26
|
-
disabled name placeholder options enabled_options
|
27
|
-
disabled_options selected with_selected multiple with_options
|
28
|
-
]
|
29
|
-
).freeze,
|
30
|
-
'field' => (
|
31
|
-
COMMON_OPTIONS + %w[
|
32
|
-
checked unchecked disabled valid name placeholder
|
33
|
-
validation_message readonly with type multiple
|
34
|
-
]
|
35
|
-
).freeze
|
36
|
-
}.freeze
|
37
|
-
SPECIFIC_PSEUDO_CLASSES = %w[
|
38
|
-
not() disabled enabled checked unchecked
|
39
|
-
].freeze
|
5
|
+
module Capybara
|
6
|
+
# Helps parsing css selector.
|
7
|
+
module CssSelector
|
8
|
+
module_function
|
40
9
|
|
41
|
-
|
10
|
+
# @param selector [String]
|
11
|
+
# @return [String]
|
12
|
+
# @example
|
13
|
+
# id('#some-id') # => some-id
|
14
|
+
# id('.some-cls') # => nil
|
15
|
+
# id('#some-id.cls') # => some-id
|
16
|
+
def id(selector)
|
17
|
+
return unless id?(selector)
|
42
18
|
|
43
|
-
|
44
|
-
|
45
|
-
# @return [Boolean]
|
46
|
-
# @example
|
47
|
-
# specific_pesudo_classes?('button', 'name') # => true
|
48
|
-
# specific_pesudo_classes?('link', 'invalid') # => false
|
49
|
-
def specific_options?(element, attribute)
|
50
|
-
SPECIFIC_OPTIONS.fetch(element, []).include?(attribute)
|
51
|
-
end
|
52
|
-
|
53
|
-
# @param pseudo_class [String]
|
54
|
-
# @return [Boolean]
|
55
|
-
# @example
|
56
|
-
# specific_pesudo_classes?('disabled') # => true
|
57
|
-
# specific_pesudo_classes?('first-of-type') # => false
|
58
|
-
def specific_pesudo_classes?(pseudo_class)
|
59
|
-
SPECIFIC_PSEUDO_CLASSES.include?(pseudo_class)
|
60
|
-
end
|
19
|
+
selector.delete('#').gsub(selector.scan(/[^\\]([>,+~.].*)/).join, '')
|
20
|
+
end
|
61
21
|
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
22
|
+
# @param selector [String]
|
23
|
+
# @return [Boolean]
|
24
|
+
# @example
|
25
|
+
# id?('#some-id') # => true
|
26
|
+
# id?('.some-cls') # => false
|
27
|
+
def id?(selector)
|
28
|
+
selector.start_with?('#')
|
29
|
+
end
|
70
30
|
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
31
|
+
# @param selector [String]
|
32
|
+
# @return [Array<String>]
|
33
|
+
# @example
|
34
|
+
# classes('#some-id') # => []
|
35
|
+
# classes('.some-cls') # => ['some-cls']
|
36
|
+
# classes('#some-id.some-cls') # => ['some-cls']
|
37
|
+
# classes('#some-id.cls1.cls2') # => ['cls1', 'cls2']
|
38
|
+
def classes(selector)
|
39
|
+
selector.scan(/\.([\w-]*)/).flatten
|
40
|
+
end
|
79
41
|
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
selector.scan(/\[(.*?)\]/).flatten.to_h do |attr|
|
88
|
-
key, value = attr.split('=')
|
89
|
-
[key, normalize_value(value)]
|
42
|
+
# @param selector [String]
|
43
|
+
# @return [Boolean]
|
44
|
+
# @example
|
45
|
+
# attribute?('[attribute]') # => true
|
46
|
+
# attribute?('attribute') # => false
|
47
|
+
def attribute?(selector)
|
48
|
+
selector.start_with?('[')
|
90
49
|
end
|
91
|
-
end
|
92
50
|
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
51
|
+
# @param selector [String]
|
52
|
+
# @return [Array<String>]
|
53
|
+
# @example
|
54
|
+
# attributes('a[foo-bar_baz]') # => {"foo-bar_baz=>nil}
|
55
|
+
# attributes('button[foo][bar=baz]') # => {"foo"=>nil, "bar"=>"'baz'"}
|
56
|
+
# attributes('table[foo=bar]') # => {"foo"=>"'bar'"}
|
57
|
+
def attributes(selector)
|
58
|
+
# Extract the inner strings of attributes.
|
59
|
+
# For example, extract the following:
|
60
|
+
# 'button[foo][bar=baz]' => 'foo][bar=baz'
|
61
|
+
inside_attributes = selector.scan(/\[(.*)\]/).flatten.join
|
62
|
+
inside_attributes.split('][').to_h do |attr|
|
63
|
+
key, value = attr.split('=')
|
64
|
+
[key, normalize_value(value)]
|
65
|
+
end
|
66
|
+
end
|
103
67
|
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
68
|
+
# @param selector [String]
|
69
|
+
# @return [Array<String>]
|
70
|
+
# @example
|
71
|
+
# pseudo_classes('button:not([disabled])') # => ['not()']
|
72
|
+
# pseudo_classes('a:enabled:not([valid])') # => ['enabled', 'not()']
|
73
|
+
def pseudo_classes(selector)
|
74
|
+
# Attributes must be excluded or else the colon in the `href`s URL
|
75
|
+
# will also be picked up as pseudo classes.
|
76
|
+
# "a:not([href='http://example.com']):enabled" => "a:not():enabled"
|
77
|
+
ignored_attribute = selector.gsub(/\[.*?\]/, '')
|
78
|
+
# "a:not():enabled" => ["not()", "enabled"]
|
79
|
+
ignored_attribute.scan(/:([^:]*)/).flatten
|
80
|
+
end
|
117
81
|
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
82
|
+
# @param selector [String]
|
83
|
+
# @return [Boolean]
|
84
|
+
# @example
|
85
|
+
# multiple_selectors?('a.cls b#id') # => true
|
86
|
+
# multiple_selectors?('a.cls') # => false
|
87
|
+
def multiple_selectors?(selector)
|
88
|
+
normalize = selector.gsub(/(\\[>,+~]|\(.*\))/, '')
|
89
|
+
normalize.match?(/[ >,+~]/)
|
90
|
+
end
|
126
91
|
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
92
|
+
# @param value [String]
|
93
|
+
# @return [Boolean, String]
|
94
|
+
# @example
|
95
|
+
# normalize_value('true') # => true
|
96
|
+
# normalize_value('false') # => false
|
97
|
+
# normalize_value(nil) # => nil
|
98
|
+
# normalize_value("foo") # => "'foo'"
|
99
|
+
def normalize_value(value)
|
100
|
+
case value
|
101
|
+
when 'true' then true
|
102
|
+
when 'false' then false
|
103
|
+
when nil then nil
|
104
|
+
else "'#{value.gsub(/"|'/, '')}'"
|
105
|
+
end
|
140
106
|
end
|
141
107
|
end
|
142
108
|
end
|
@@ -41,9 +41,7 @@ module RuboCop
|
|
41
41
|
# which the last selector points.
|
42
42
|
next unless (selector = last_selector(arg))
|
43
43
|
next unless (action = specific_action(selector))
|
44
|
-
next unless
|
45
|
-
action)
|
46
|
-
next unless CapybaraHelp.specific_pseudo_classes?(arg)
|
44
|
+
next unless replaceable?(node, arg, action)
|
47
45
|
|
48
46
|
range = offense_range(node, node.receiver)
|
49
47
|
add_offense(range, message: message(action, selector))
|
@@ -56,6 +54,18 @@ module RuboCop
|
|
56
54
|
SPECIFIC_ACTION[last_selector(selector)]
|
57
55
|
end
|
58
56
|
|
57
|
+
def replaceable?(node, arg, action)
|
58
|
+
replaceable_attributes?(arg) &&
|
59
|
+
CapybaraHelp.replaceable_option?(node.receiver, arg, action) &&
|
60
|
+
CapybaraHelp.replaceable_pseudo_classes?(arg)
|
61
|
+
end
|
62
|
+
|
63
|
+
def replaceable_attributes?(selector)
|
64
|
+
CapybaraHelp.replaceable_attributes?(
|
65
|
+
CssSelector.attributes(selector)
|
66
|
+
)
|
67
|
+
end
|
68
|
+
|
59
69
|
def supported_selector?(selector)
|
60
70
|
!selector.match?(/[>,+~]/)
|
61
71
|
end
|
@@ -27,8 +27,14 @@ module RuboCop
|
|
27
27
|
(send _ :find (str $_) ...)
|
28
28
|
PATTERN
|
29
29
|
|
30
|
+
# @!method class_options(node)
|
31
|
+
def_node_search :class_options, <<~PATTERN
|
32
|
+
(pair (sym :class) $_ ...)
|
33
|
+
PATTERN
|
34
|
+
|
30
35
|
def on_send(node)
|
31
36
|
find_argument(node) do |arg|
|
37
|
+
next if CssSelector.pseudo_classes(arg).any?
|
32
38
|
next if CssSelector.multiple_selectors?(arg)
|
33
39
|
|
34
40
|
on_attr(node, arg) if attribute?(arg)
|
@@ -39,28 +45,57 @@ module RuboCop
|
|
39
45
|
private
|
40
46
|
|
41
47
|
def on_attr(node, arg)
|
42
|
-
|
48
|
+
attrs = CssSelector.attributes(arg)
|
49
|
+
return unless (id = attrs['id'])
|
50
|
+
return if attrs['class']
|
43
51
|
|
44
52
|
register_offense(node, replaced_arguments(arg, id))
|
45
53
|
end
|
46
54
|
|
47
55
|
def on_id(node, arg)
|
48
|
-
|
56
|
+
return if CssSelector.attributes(arg).any?
|
57
|
+
|
58
|
+
id = CssSelector.id(arg)
|
59
|
+
register_offense(node, "'#{id}'",
|
60
|
+
CssSelector.classes(arg.sub("##{id}", '')))
|
49
61
|
end
|
50
62
|
|
51
63
|
def attribute?(arg)
|
52
64
|
CssSelector.attribute?(arg) &&
|
53
|
-
|
65
|
+
CapybaraHelp.common_attributes?(arg)
|
54
66
|
end
|
55
67
|
|
56
|
-
def register_offense(node,
|
68
|
+
def register_offense(node, id, classes = [])
|
57
69
|
add_offense(offense_range(node)) do |corrector|
|
58
70
|
corrector.replace(node.loc.selector, 'find_by_id')
|
59
71
|
corrector.replace(node.first_argument.loc.expression,
|
60
|
-
|
72
|
+
id.delete('\\'))
|
73
|
+
unless classes.compact.empty?
|
74
|
+
autocorrect_classes(corrector, node, classes)
|
75
|
+
end
|
61
76
|
end
|
62
77
|
end
|
63
78
|
|
79
|
+
def autocorrect_classes(corrector, node, classes)
|
80
|
+
if (options = class_options(node).first)
|
81
|
+
append_options(classes, options)
|
82
|
+
corrector.replace(options, classes.to_s)
|
83
|
+
else
|
84
|
+
corrector.insert_after(node.first_argument,
|
85
|
+
keyword_argument_class(classes))
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def append_options(classes, options)
|
90
|
+
classes << options.value if options.str_type?
|
91
|
+
options.each_value { |v| classes << v.value } if options.array_type?
|
92
|
+
end
|
93
|
+
|
94
|
+
def keyword_argument_class(classes)
|
95
|
+
value = classes.size > 1 ? classes.to_s : "'#{classes.first}'"
|
96
|
+
", class: #{value}"
|
97
|
+
end
|
98
|
+
|
64
99
|
def replaced_arguments(arg, id)
|
65
100
|
options = to_options(CssSelector.attributes(arg))
|
66
101
|
options.empty? ? id : "#{id}, #{options}"
|
@@ -46,8 +46,7 @@ module RuboCop
|
|
46
46
|
first_argument(node) do |arg|
|
47
47
|
next unless (matcher = specific_matcher(arg))
|
48
48
|
next if CssSelector.multiple_selectors?(arg)
|
49
|
-
next unless
|
50
|
-
next unless CapybaraHelp.specific_pseudo_classes?(arg)
|
49
|
+
next unless replaceable?(node, arg, matcher)
|
51
50
|
|
52
51
|
add_offense(node, message: message(node, matcher))
|
53
52
|
end
|
@@ -60,6 +59,18 @@ module RuboCop
|
|
60
59
|
SPECIFIC_MATCHER[splitted_arg]
|
61
60
|
end
|
62
61
|
|
62
|
+
def replaceable?(node, arg, matcher)
|
63
|
+
replaceable_attributes?(arg) &&
|
64
|
+
CapybaraHelp.replaceable_option?(node, arg, matcher) &&
|
65
|
+
CapybaraHelp.replaceable_pseudo_classes?(arg)
|
66
|
+
end
|
67
|
+
|
68
|
+
def replaceable_attributes?(selector)
|
69
|
+
CapybaraHelp.replaceable_attributes?(
|
70
|
+
CssSelector.attributes(selector)
|
71
|
+
)
|
72
|
+
end
|
73
|
+
|
63
74
|
def message(node, matcher)
|
64
75
|
format(MSG,
|
65
76
|
good_matcher: good_matcher(node, matcher),
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rubocop-capybara
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.17.
|
4
|
+
version: 2.17.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Yudai Takada
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2023-02-16 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rubocop
|
@@ -75,7 +75,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
75
75
|
- !ruby/object:Gem::Version
|
76
76
|
version: '0'
|
77
77
|
requirements: []
|
78
|
-
rubygems_version: 3.
|
78
|
+
rubygems_version: 3.4.3
|
79
79
|
signing_key:
|
80
80
|
specification_version: 4
|
81
81
|
summary: Code style checking for Capybara test files
|