-
-
Notifications
You must be signed in to change notification settings - Fork 270
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #1340 from ydah/add_specific_finders
Add new `RSpec/Capybara/SpecificFinders` cop
- Loading branch information
Showing
11 changed files
with
337 additions
and
25 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,86 @@ | ||
# frozen_string_literal: true | ||
|
||
module RuboCop | ||
module Cop | ||
module RSpec | ||
module Capybara | ||
# Checks if there is a more specific finder offered by Capybara. | ||
# | ||
# @example | ||
# # bad | ||
# find('#some-id') | ||
# find('[visible][id=some-id]') | ||
# | ||
# # good | ||
# find_by_id('some-id') | ||
# find_by_id('some-id', visible: true) | ||
# | ||
class SpecificFinders < Base | ||
extend AutoCorrector | ||
|
||
include RangeHelp | ||
|
||
MSG = 'Prefer `find_by` over `find`.' | ||
RESTRICT_ON_SEND = %i[find].freeze | ||
|
||
# @!method find_argument(node) | ||
def_node_matcher :find_argument, <<~PATTERN | ||
(send _ :find (str $_) ...) | ||
PATTERN | ||
|
||
def on_send(node) | ||
find_argument(node) do |arg| | ||
next if CssSelector.multiple_selectors?(arg) | ||
|
||
on_attr(node, arg) if attribute?(arg) | ||
on_id(node, arg) if CssSelector.id?(arg) | ||
end | ||
end | ||
|
||
private | ||
|
||
def on_attr(node, arg) | ||
return unless (id = CssSelector.attributes(arg)['id']) | ||
|
||
register_offense(node, replaced_argments(arg, id)) | ||
end | ||
|
||
def on_id(node, arg) | ||
register_offense(node, "'#{arg.to_s.delete('#')}'") | ||
end | ||
|
||
def attribute?(arg) | ||
CssSelector.attribute?(arg) && | ||
CssSelector.common_attributes?(arg) | ||
end | ||
|
||
def register_offense(node, arg_replacemenet) | ||
add_offense(offense_range(node)) do |corrector| | ||
corrector.replace(node.loc.selector, 'find_by_id') | ||
corrector.replace(node.first_argument.loc.expression, | ||
arg_replacemenet) | ||
end | ||
end | ||
|
||
def replaced_argments(arg, id) | ||
options = to_options(CssSelector.attributes(arg)) | ||
options.empty? ? id : "#{id}, #{options}" | ||
end | ||
|
||
def to_options(attrs) | ||
attrs.each.map do |key, value| | ||
next if key == 'id' | ||
|
||
"#{key}: #{value}" | ||
end.compact.join(', ') | ||
end | ||
|
||
def offense_range(node) | ||
range_between(node.loc.selector.begin_pos, | ||
node.loc.end.end_pos) | ||
end | ||
end | ||
end | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
# frozen_string_literal: true | ||
|
||
module RuboCop | ||
module Cop | ||
module RSpec | ||
# Helps parsing css selector. | ||
module CssSelector | ||
COMMON_OPTIONS = %w[ | ||
above below left_of right_of near count minimum maximum between text | ||
id class style visible obscured exact exact_text normalize_ws match | ||
wait filter_set focused | ||
].freeze | ||
|
||
module_function | ||
|
||
# @param selector [String] | ||
# @return [Boolean] | ||
# @example | ||
# id?('#some-id') # => true | ||
# id?('.some-class') # => false | ||
def id?(selector) | ||
selector.start_with?('#') | ||
end | ||
|
||
# @param selector [String] | ||
# @return [Boolean] | ||
# @example | ||
# attribute?('[attribute]') # => true | ||
# attribute?('attribute') # => false | ||
def attribute?(selector) | ||
selector.start_with?('[') | ||
end | ||
|
||
# @param selector [String] | ||
# @return [Array<String>] | ||
# @example | ||
# attributes('a[foo-bar_baz]') # => {"foo-bar_baz=>true} | ||
# attributes('button[foo][bar]') # => {"foo"=>true, "bar"=>true} | ||
# attributes('table[foo=bar]') # => {"foo"=>"'bar'"} | ||
def attributes(selector) | ||
selector.scan(/\[(.*?)\]/).flatten.to_h do |attr| | ||
key, value = attr.split('=') | ||
[key, normalize_value(value)] | ||
end | ||
end | ||
|
||
# @param selector [String] | ||
# @return [Boolean] | ||
# @example | ||
# common_attributes?('a[focused]') # => true | ||
# common_attributes?('button[focused][visible]') # => true | ||
# common_attributes?('table[id=some-id]') # => true | ||
# common_attributes?('h1[invalid]') # => false | ||
def common_attributes?(selector) | ||
attributes(selector).keys.difference(COMMON_OPTIONS).none? | ||
end | ||
|
||
# @param selector [String] | ||
# @return [Boolean] | ||
# @example | ||
# multiple_selectors?('a.cls b#id') # => true | ||
# multiple_selectors?('a.cls') # => false | ||
def multiple_selectors?(selector) | ||
selector.match?(/[ >,+]/) | ||
end | ||
|
||
# @param selector [String] | ||
# @return [Boolean, String] | ||
# @example | ||
# normalize_value('true') # => true | ||
# normalize_value('false') # => false | ||
# normalize_value(nil) # => false | ||
# normalize_value("foo") # => "'foo'" | ||
def normalize_value(value) | ||
case value | ||
when 'true' then true | ||
when 'false' then false | ||
when nil then true | ||
else "'#{value}'" | ||
end | ||
end | ||
end | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.