rubocop-capybara 2.17.0 → 2.17.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
[![Gem Version](https://badge.fury.io/rb/rubocop-capybara.svg)](https://rubygems.org/gems/rubocop-capybara)
|
5
5
|
![CI](https://github.com/rubocop/rubocop-capybara/workflows/CI/badge.svg)
|
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
|