Merge pull request #14101 from Homebrew/dependabot/bundler/Library/Homebrew/rubocop-rspec-2.15.0

build(deps): bump rubocop-rspec from 2.13.2 to 2.15.0 in /Library/Homebrew
This commit is contained in:
Bo Anderson 2022-11-05 17:23:10 +00:00 committed by GitHub
commit 57df913974
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
147 changed files with 1589 additions and 452 deletions

View File

@ -151,7 +151,7 @@ GEM
activesupport (>= 4.2.0)
rack (>= 1.1)
rubocop (>= 1.33.0, < 2.0)
rubocop-rspec (2.13.2)
rubocop-rspec (2.15.0)
rubocop (~> 1.33)
rubocop-sorbet (0.6.11)
rubocop (>= 0.90.0)

View File

@ -172,6 +172,47 @@ end
RuboCop::Cop::RSpec::Capybara::FeatureMethods::MAP = T.let(T.unsafe(nil), Hash)
RuboCop::Cop::RSpec::Capybara::FeatureMethods::MSG = T.let(T.unsafe(nil), String)
class RuboCop::Cop::RSpec::Capybara::NegationMatcher < ::RuboCop::Cop::RSpec::Base
include ::RuboCop::Cop::ConfigurableEnforcedStyle
extend ::RuboCop::Cop::AutoCorrector
def have_no?(param0 = T.unsafe(nil)); end
def not_to?(param0 = T.unsafe(nil)); end
def on_send(node); end
private
def message(matcher); end
def offense?(node); end
def offense_range(node); end
def replaced_matcher(matcher); end
def replaced_runner; end
end
RuboCop::Cop::RSpec::Capybara::NegationMatcher::CAPYBARA_MATCHERS = T.let(T.unsafe(nil), Array)
RuboCop::Cop::RSpec::Capybara::NegationMatcher::MSG = T.let(T.unsafe(nil), String)
RuboCop::Cop::RSpec::Capybara::NegationMatcher::NEGATIVE_MATCHERS = T.let(T.unsafe(nil), Set)
RuboCop::Cop::RSpec::Capybara::NegationMatcher::POSITIVE_MATCHERS = T.let(T.unsafe(nil), Set)
RuboCop::Cop::RSpec::Capybara::NegationMatcher::RESTRICT_ON_SEND = T.let(T.unsafe(nil), Set)
class RuboCop::Cop::RSpec::Capybara::SpecificActions < ::RuboCop::Cop::RSpec::Base
def click_on_selector(param0 = T.unsafe(nil)); end
def on_send(node); end
private
def good_action(action); end
def last_selector(arg); end
def message(action, selector); end
def offense_range(node, receiver); end
def specific_action(selector); end
def supported_selector?(selector); end
end
RuboCop::Cop::RSpec::Capybara::SpecificActions::MSG = T.let(T.unsafe(nil), String)
RuboCop::Cop::RSpec::Capybara::SpecificActions::RESTRICT_ON_SEND = T.let(T.unsafe(nil), Array)
RuboCop::Cop::RSpec::Capybara::SpecificActions::SPECIFIC_ACTION = T.let(T.unsafe(nil), Hash)
class RuboCop::Cop::RSpec::Capybara::SpecificFinders < ::RuboCop::Cop::RSpec::Base
include ::RuboCop::Cop::RangeHelp
extend ::RuboCop::Cop::AutoCorrector
@ -195,28 +236,21 @@ RuboCop::Cop::RSpec::Capybara::SpecificFinders::MSG = T.let(T.unsafe(nil), Strin
RuboCop::Cop::RSpec::Capybara::SpecificFinders::RESTRICT_ON_SEND = T.let(T.unsafe(nil), Array)
class RuboCop::Cop::RSpec::Capybara::SpecificMatcher < ::RuboCop::Cop::RSpec::Base
include ::RuboCop::Cop::RSpec::CapybaraHelp
def first_argument(param0 = T.unsafe(nil)); end
def on_send(node); end
def option?(param0, param1); end
private
def good_matcher(node, matcher); end
def message(node, matcher); end
def replaceable_matcher?(node, matcher, attrs); end
def replaceable_pseudo_class?(pseudo_class, arg); end
def replaceable_pseudo_class_not?(arg); end
def replaceable_to_have_link?(node, attrs); end
def specific_matcher(arg); end
def specific_matcher_option?(node, arg, matcher); end
def specific_matcher_pseudo_classes?(arg); end
end
RuboCop::Cop::RSpec::Capybara::SpecificMatcher::MSG = T.let(T.unsafe(nil), String)
RuboCop::Cop::RSpec::Capybara::SpecificMatcher::RESTRICT_ON_SEND = T.let(T.unsafe(nil), Array)
RuboCop::Cop::RSpec::Capybara::SpecificMatcher::SPECIFIC_MATCHER = T.let(T.unsafe(nil), Hash)
RuboCop::Cop::RSpec::Capybara::SpecificMatcher::SPECIFIC_MATCHER_OPTIONS = T.let(T.unsafe(nil), Hash)
RuboCop::Cop::RSpec::Capybara::SpecificMatcher::SPECIFIC_MATCHER_PSEUDO_CLASSES = T.let(T.unsafe(nil), Array)
class RuboCop::Cop::RSpec::Capybara::VisibilityMatcher < ::RuboCop::Cop::RSpec::Base
def on_send(node); end
@ -233,6 +267,28 @@ RuboCop::Cop::RSpec::Capybara::VisibilityMatcher::MSG_FALSE = T.let(T.unsafe(nil
RuboCop::Cop::RSpec::Capybara::VisibilityMatcher::MSG_TRUE = T.let(T.unsafe(nil), String)
RuboCop::Cop::RSpec::Capybara::VisibilityMatcher::RESTRICT_ON_SEND = T.let(T.unsafe(nil), Array)
module RuboCop::Cop::RSpec::CapybaraHelp
private
def include_option?(node, option); end
def replaceable_element?(node, element, attrs); end
def replaceable_pseudo_class?(pseudo_class, locator); end
def replaceable_pseudo_class_not?(locator); end
def replaceable_to_link?(node, attrs); end
def specific_option?(node, locator, element); end
def specific_pseudo_classes?(locator); end
class << self
def include_option?(node, option); end
def replaceable_element?(node, element, attrs); end
def replaceable_pseudo_class?(pseudo_class, locator); end
def replaceable_pseudo_class_not?(locator); end
def replaceable_to_link?(node, attrs); end
def specific_option?(node, locator, element); end
def specific_pseudo_classes?(locator); end
end
end
class RuboCop::Cop::RSpec::ChangeByZero < ::RuboCop::Cop::RSpec::Base
extend ::RuboCop::Cop::AutoCorrector
@ -328,6 +384,8 @@ module RuboCop::Cop::RSpec::CssSelector
def multiple_selectors?(selector); end
def normalize_value(value); end
def pseudo_classes(selector); end
def specific_options?(element, attribute); end
def specific_pesudo_classes?(pseudo_class); end
class << self
def attribute?(selector); end
@ -337,10 +395,14 @@ module RuboCop::Cop::RSpec::CssSelector
def multiple_selectors?(selector); end
def normalize_value(value); end
def pseudo_classes(selector); end
def specific_options?(element, attribute); end
def specific_pesudo_classes?(pseudo_class); end
end
end
RuboCop::Cop::RSpec::CssSelector::COMMON_OPTIONS = T.let(T.unsafe(nil), Array)
RuboCop::Cop::RSpec::CssSelector::SPECIFIC_OPTIONS = T.let(T.unsafe(nil), Hash)
RuboCop::Cop::RSpec::CssSelector::SPECIFIC_PSEUDO_CLASSES = T.let(T.unsafe(nil), Array)
class RuboCop::Cop::RSpec::DescribeClass < ::RuboCop::Cop::RSpec::Base
include ::RuboCop::Cop::RSpec::TopLevelGroup
@ -568,11 +630,15 @@ class RuboCop::Cop::RSpec::ExampleWording < ::RuboCop::Cop::RSpec::Base
def custom_transform; end
def docstring(node); end
def ignored_words; end
def insufficient_docstring?(description_node); end
def insufficient_examples; end
def preprocess(message); end
def replacement_text(node); end
def text(node); end
end
RuboCop::Cop::RSpec::ExampleWording::IT_PREFIX = T.let(T.unsafe(nil), Regexp)
RuboCop::Cop::RSpec::ExampleWording::MSG_INSUFFICIENT_DESCRIPTION = T.let(T.unsafe(nil), String)
RuboCop::Cop::RSpec::ExampleWording::MSG_IT = T.let(T.unsafe(nil), String)
RuboCop::Cop::RSpec::ExampleWording::MSG_SHOULD = T.let(T.unsafe(nil), String)
RuboCop::Cop::RSpec::ExampleWording::SHOULD_PREFIX = T.let(T.unsafe(nil), Regexp)
@ -696,6 +762,31 @@ end
RuboCop::Cop::RSpec::FactoryBot::AttributeDefinedStatically::MSG = T.let(T.unsafe(nil), String)
class RuboCop::Cop::RSpec::FactoryBot::ConsistentParenthesesStyle < ::RuboCop::Cop::RSpec::Base
include ::RuboCop::Cop::ConfigurableEnforcedStyle
include ::RuboCop::RSpec::FactoryBot::Language
extend ::RuboCop::Cop::AutoCorrector
def ambiguous_without_parentheses?(node); end
def factory_call(param0 = T.unsafe(nil)); end
def on_send(node); end
def process_with_parentheses(node); end
def process_without_parentheses(node); end
private
def remove_parentheses(corrector, node); end
class << self
def autocorrect_incompatible_with; end
end
end
RuboCop::Cop::RSpec::FactoryBot::ConsistentParenthesesStyle::FACTORY_CALLS = T.let(T.unsafe(nil), Set)
RuboCop::Cop::RSpec::FactoryBot::ConsistentParenthesesStyle::MSG_OMIT_PARENS = T.let(T.unsafe(nil), String)
RuboCop::Cop::RSpec::FactoryBot::ConsistentParenthesesStyle::MSG_REQUIRE_PARENS = T.let(T.unsafe(nil), String)
RuboCop::Cop::RSpec::FactoryBot::ConsistentParenthesesStyle::RESTRICT_ON_SEND = T.let(T.unsafe(nil), Set)
class RuboCop::Cop::RSpec::FactoryBot::CreateList < ::RuboCop::Cop::RSpec::Base
include ::RuboCop::Cop::ConfigurableEnforcedStyle
include ::RuboCop::RSpec::FactoryBot::Language
@ -803,14 +894,16 @@ class RuboCop::Cop::RSpec::FilePath < ::RuboCop::Cop::RSpec::Base
def camel_to_snake_case(string); end
def custom_transform; end
def ensure_correct_file_path(send_node, example_group, arguments); end
def expanded_file_path; end
def expected_path(constant); end
def filename_ends_with?(pattern); end
def ignore_methods?; end
def name_pattern(method_name); end
def pattern_for(example_group, method_name); end
def pattern_for(example_group, arguments); end
def pattern_for_spec_suffix_only; end
def relevant_rubocop_rspec_file?(_file); end
def routing_spec?(args); end
def routing_spec_path?; end
def spec_suffix_only?; end
end
@ -922,17 +1015,26 @@ class RuboCop::Cop::RSpec::ImplicitSubject < ::RuboCop::Cop::RSpec::Base
include ::RuboCop::Cop::ConfigurableEnforcedStyle
extend ::RuboCop::Cop::AutoCorrector
def explicit_unnamed_subject?(param0 = T.unsafe(nil)); end
def implicit_subject?(param0 = T.unsafe(nil)); end
def on_send(node); end
private
def allowed_by_style?(example); end
def autocorrect(corrector, node); end
def valid_usage?(node); end
def example_of(node); end
def implicit_subject_in_non_its?(node); end
def implicit_subject_in_non_its_and_non_single_line?(node); end
def implicit_subject_in_non_its_and_non_single_statement?(node); end
def invalid?(node); end
def its?(node); end
def message(_node); end
def single_line?(node); end
def single_statement?(node); end
end
RuboCop::Cop::RSpec::ImplicitSubject::MSG = T.let(T.unsafe(nil), String)
RuboCop::Cop::RSpec::ImplicitSubject::MSG_REQUIRE_EXPLICIT = T.let(T.unsafe(nil), String)
RuboCop::Cop::RSpec::ImplicitSubject::MSG_REQUIRE_IMPLICIT = T.let(T.unsafe(nil), String)
RuboCop::Cop::RSpec::ImplicitSubject::RESTRICT_ON_SEND = T.let(T.unsafe(nil), Array)
module RuboCop::Cop::RSpec::InflectedHelper
@ -1061,12 +1163,14 @@ class RuboCop::Cop::RSpec::LetBeforeExamples < ::RuboCop::Cop::RSpec::Base
extend ::RuboCop::Cop::AutoCorrector
def example_or_group?(param0 = T.unsafe(nil)); end
def include_examples?(param0 = T.unsafe(nil)); end
def on_block(node); end
private
def autocorrect(corrector, node, first_example); end
def check_let_declarations(node); end
def example_group_with_include_examples?(body); end
def find_first_example(node); end
def multiline_block?(block); end
end
@ -1199,11 +1303,23 @@ end
RuboCop::Cop::RSpec::MultipleSubjects::MSG = T.let(T.unsafe(nil), String)
class RuboCop::Cop::RSpec::NamedSubject < ::RuboCop::Cop::RSpec::Base
include ::RuboCop::Cop::ConfigurableEnforcedStyle
def example_or_hook_block?(param0 = T.unsafe(nil)); end
def ignored_shared_example?(node); end
def on_block(node); end
def shared_example?(param0 = T.unsafe(nil)); end
def subject_usage(param0); end
private
def allow_explicit_subject?(node); end
def always?; end
def check_explicit_subject(node); end
def find_subject(block_node); end
def ignored_shared_example?(node); end
def named_only?(node); end
def nearest_subject(node); end
def subject_definition_is_named?(node); end
end
RuboCop::Cop::RSpec::NamedSubject::MSG = T.let(T.unsafe(nil), String)
@ -1235,8 +1351,11 @@ RuboCop::Cop::RSpec::NestedGroups::DEPRECATION_WARNING = T.let(T.unsafe(nil), St
RuboCop::Cop::RSpec::NestedGroups::MSG = T.let(T.unsafe(nil), String)
class RuboCop::Cop::RSpec::NoExpectationExample < ::RuboCop::Cop::RSpec::Base
def including_any_expectation?(param0); end
def including_any_skip_example?(param0); end
include ::RuboCop::Cop::AllowedPattern
include ::RuboCop::Cop::RSpec::SkipOrPending
def includes_expectation?(param0); end
def includes_skip_example?(param0); end
def on_block(node); end
def on_numblock(node); end
def regular_or_focused_example?(param0 = T.unsafe(nil)); end
@ -1273,11 +1392,11 @@ end
RuboCop::Cop::RSpec::OverwritingSetup::MSG = T.let(T.unsafe(nil), String)
class RuboCop::Cop::RSpec::Pending < ::RuboCop::Cop::RSpec::Base
include ::RuboCop::Cop::RSpec::SkipOrPending
def on_send(node); end
def pending_block?(param0 = T.unsafe(nil)); end
def skip_or_pending?(param0 = T.unsafe(nil)); end
def skippable?(param0 = T.unsafe(nil)); end
def skipped_in_metadata?(param0 = T.unsafe(nil)); end
private
@ -1369,6 +1488,26 @@ end
RuboCop::Cop::RSpec::Rails::HttpStatus::SymbolicStyleChecker::MSG = T.let(T.unsafe(nil), String)
class RuboCop::Cop::RSpec::Rails::InferredSpecType < ::RuboCop::Cop::RSpec::Base
extend ::RuboCop::Cop::AutoCorrector
def describe_with_type(param0 = T.unsafe(nil)); end
def on_block(node); end
def on_numblock(node); end
private
def autocorrect(corrector, node); end
def detect_removable_node(node); end
def file_path; end
def inferences; end
def inferred_type?(node); end
def inferred_type_from_file_path; end
def remove_range(node); end
end
RuboCop::Cop::RSpec::Rails::InferredSpecType::MSG = T.let(T.unsafe(nil), String)
class RuboCop::Cop::RSpec::ReceiveCounts < ::RuboCop::Cop::RSpec::Base
extend ::RuboCop::Cop::AutoCorrector
@ -1407,7 +1546,9 @@ class RuboCop::Cop::RSpec::RepeatedDescription < ::RuboCop::Cop::RSpec::Base
private
def example_signature(example); end
def its_signature(example); end
def repeated_descriptions(node); end
def repeated_its(node); end
end
RuboCop::Cop::RSpec::RepeatedDescription::MSG = T.let(T.unsafe(nil), String)
@ -1606,6 +1747,35 @@ end
RuboCop::Cop::RSpec::SingleArgumentMessageChain::MSG = T.let(T.unsafe(nil), String)
RuboCop::Cop::RSpec::SingleArgumentMessageChain::RESTRICT_ON_SEND = T.let(T.unsafe(nil), Array)
module RuboCop::Cop::RSpec::SkipOrPending
extend ::RuboCop::AST::NodePattern::Macros
def skip_or_pending?(param0 = T.unsafe(nil)); end
def skipped_in_metadata?(param0 = T.unsafe(nil)); end
end
class RuboCop::Cop::RSpec::SortMetadata < ::RuboCop::Cop::RSpec::Base
include ::RuboCop::Cop::RangeHelp
extend ::RuboCop::Cop::AutoCorrector
def metadata_in_block(param0, param1); end
def on_block(node); end
def on_numblock(node); end
def rspec_configure(param0 = T.unsafe(nil)); end
def rspec_metadata(param0 = T.unsafe(nil)); end
private
def crime_scene(symbols, pairs); end
def investigate(symbols, pairs); end
def replacement(symbols, pairs); end
def sort_pairs(pairs); end
def sort_symbols(symbols); end
def sorted?(symbols, pairs); end
end
RuboCop::Cop::RSpec::SortMetadata::MSG = T.let(T.unsafe(nil), String)
class RuboCop::Cop::RSpec::StubbedMock < ::RuboCop::Cop::RSpec::Base
def configured_response?(param0 = T.unsafe(nil)); end
def expectation(param0 = T.unsafe(nil)); end
@ -1889,6 +2059,7 @@ module RuboCop::RSpec::FactoryBot::Language
def factory_bot?(param0 = T.unsafe(nil)); end
end
RuboCop::RSpec::FactoryBot::Language::METHODS = T.let(T.unsafe(nil), Set)
RuboCop::RSpec::FactoryBot::RESERVED_METHODS = T.let(T.unsafe(nil), Array)
RuboCop::RSpec::FactoryBot::UNPROXIED_METHODS = T.let(T.unsafe(nil), Array)
@ -1979,6 +2150,8 @@ module RuboCop::RSpec::Language::HookScopes
end
end
RuboCop::RSpec::Language::HookScopes::ALL = T.let(T.unsafe(nil), Array)
module RuboCop::RSpec::Language::Hooks
class << self
def all(element); end
@ -2005,6 +2178,8 @@ module RuboCop::RSpec::Language::Runners
end
end
RuboCop::RSpec::Language::Runners::ALL = T.let(T.unsafe(nil), Array)
module RuboCop::RSpec::Language::SharedGroups
class << self
def all(element); end

View File

@ -6,7 +6,7 @@ require "cmd/shared_examples/args_parse"
describe "brew bump" do
it_behaves_like "parseable arguments"
describe "formula", :integration_test, :needs_network, :needs_homebrew_curl do
describe "formula", :integration_test, :needs_homebrew_curl, :needs_network do
it "returns data for single valid specified formula" do
install_test_formula "testball"

View File

@ -4,7 +4,7 @@
require "utils/repology"
describe Repology do
describe "single_package_query", :needs_network, :needs_homebrew_curl do
describe "single_package_query", :needs_homebrew_curl, :needs_network do
it "returns nil for non-existent package" do
response = described_class.single_package_query("invalidName", repository: "homebrew")
@ -19,7 +19,7 @@ describe Repology do
end
end
describe "parse_api_response", :needs_network, :needs_homebrew_curl do
describe "parse_api_response", :needs_homebrew_curl, :needs_network do
it "returns a hash of data" do
limit = 1
start_with = "x"

View File

@ -107,7 +107,7 @@ $:.unshift File.expand_path("#{__dir__}/../#{RUBY_ENGINE}/#{Gem.ruby_api_version
$:.unshift File.expand_path("#{__dir__}/../#{RUBY_ENGINE}/#{Gem.ruby_api_version}/gems/rubocop-1.35.1/lib")
$:.unshift File.expand_path("#{__dir__}/../#{RUBY_ENGINE}/#{Gem.ruby_api_version}/gems/rubocop-performance-1.15.0/lib")
$:.unshift File.expand_path("#{__dir__}/../#{RUBY_ENGINE}/#{Gem.ruby_api_version}/gems/rubocop-rails-2.16.1/lib")
$:.unshift File.expand_path("#{__dir__}/../#{RUBY_ENGINE}/#{Gem.ruby_api_version}/gems/rubocop-rspec-2.13.2/lib")
$:.unshift File.expand_path("#{__dir__}/../#{RUBY_ENGINE}/#{Gem.ruby_api_version}/gems/rubocop-rspec-2.15.0/lib")
$:.unshift File.expand_path("#{__dir__}/../#{RUBY_ENGINE}/#{Gem.ruby_api_version}/gems/rubocop-sorbet-0.6.11/lib")
$:.unshift File.expand_path("#{__dir__}/../#{RUBY_ENGINE}/#{Gem.ruby_api_version}/gems/ruby-macho-3.0.0/lib")
$:.unshift File.expand_path("#{__dir__}/../#{RUBY_ENGINE}/#{Gem.ruby_api_version}/gems/simplecov-html-0.12.3/lib")

View File

@ -1,158 +0,0 @@
# frozen_string_literal: true
module RuboCop
module Cop
module RSpec
module Capybara
# Checks for there is a more specific matcher offered by Capybara.
#
# @example
#
# # bad
# expect(page).to have_selector('button')
# expect(page).to have_no_selector('button.cls')
# expect(page).to have_css('button')
# expect(page).to have_no_css('a.cls', href: 'http://example.com')
# expect(page).to have_css('table.cls')
# expect(page).to have_css('select')
# expect(page).to have_css('input', exact_text: 'foo')
#
# # good
# expect(page).to have_button
# expect(page).to have_no_button(class: 'cls')
# expect(page).to have_button
# expect(page).to have_no_link('foo', class: 'cls', href: 'http://example.com')
# expect(page).to have_table(class: 'cls')
# expect(page).to have_select
# expect(page).to have_field('foo')
#
class SpecificMatcher < Base # rubocop:disable Metrics/ClassLength
MSG = 'Prefer `%<good_matcher>s` over `%<bad_matcher>s`.'
RESTRICT_ON_SEND = %i[have_selector have_no_selector have_css
have_no_css].freeze
SPECIFIC_MATCHER = {
'button' => 'button',
'a' => 'link',
'table' => 'table',
'select' => 'select',
'input' => 'field'
}.freeze
SPECIFIC_MATCHER_OPTIONS = {
'button' => (
CssSelector::COMMON_OPTIONS + %w[disabled name value title type]
).freeze,
'link' => (
CssSelector::COMMON_OPTIONS + %w[href alt title download]
).freeze,
'table' => (
CssSelector::COMMON_OPTIONS + %w[
caption with_cols cols with_rows rows
]
).freeze,
'select' => (
CssSelector::COMMON_OPTIONS + %w[
disabled name placeholder options enabled_options
disabled_options selected with_selected multiple with_options
]
).freeze,
'field' => (
CssSelector::COMMON_OPTIONS + %w[
checked unchecked disabled valid name placeholder
validation_message readonly with type multiple
]
).freeze
}.freeze
SPECIFIC_MATCHER_PSEUDO_CLASSES = %w[
not() disabled enabled checked unchecked
].freeze
# @!method first_argument(node)
def_node_matcher :first_argument, <<-PATTERN
(send nil? _ (str $_) ... )
PATTERN
# @!method option?(node)
def_node_search :option?, <<-PATTERN
(pair (sym %) _)
PATTERN
def on_send(node)
first_argument(node) do |arg|
next unless (matcher = specific_matcher(arg))
next if CssSelector.multiple_selectors?(arg)
next unless specific_matcher_option?(node, arg, matcher)
next unless specific_matcher_pseudo_classes?(arg)
add_offense(node, message: message(node, matcher))
end
end
private
def specific_matcher(arg)
splitted_arg = arg[/^\w+/, 0]
SPECIFIC_MATCHER[splitted_arg]
end
def specific_matcher_option?(node, arg, matcher)
attrs = CssSelector.attributes(arg).keys
return true if attrs.empty?
return false unless replaceable_matcher?(node, matcher, attrs)
attrs.all? do |attr|
SPECIFIC_MATCHER_OPTIONS.fetch(matcher, []).include?(attr)
end
end
def specific_matcher_pseudo_classes?(arg)
CssSelector.pseudo_classes(arg).all? do |pseudo_class|
replaceable_pseudo_class?(pseudo_class, arg)
end
end
def replaceable_pseudo_class?(pseudo_class, arg)
unless SPECIFIC_MATCHER_PSEUDO_CLASSES.include?(pseudo_class)
return false
end
case pseudo_class
when 'not()' then replaceable_pseudo_class_not?(arg)
else true
end
end
def replaceable_pseudo_class_not?(arg)
arg.scan(/not\(.*?\)/).all? do |not_arg|
CssSelector.attributes(not_arg).values.all? do |v|
v.is_a?(TrueClass) || v.is_a?(FalseClass)
end
end
end
def replaceable_matcher?(node, matcher, attrs)
case matcher
when 'link' then replaceable_to_have_link?(node, attrs)
else true
end
end
def replaceable_to_have_link?(node, attrs)
option?(node, :href) || attrs.include?('href')
end
def message(node, matcher)
format(MSG,
good_matcher: good_matcher(node, matcher),
bad_matcher: node.method_name)
end
def good_matcher(node, matcher)
node.method_name
.to_s
.gsub(/selector|css/, matcher.to_s)
end
end
end
end
end
end

View File

@ -1,100 +0,0 @@
# frozen_string_literal: true
module RuboCop
module Cop
module RSpec
# Checks for usage of implicit subject (`is_expected` / `should`).
#
# This cop can be configured using the `EnforcedStyle` option
#
# @example `EnforcedStyle: single_line_only` (default)
# # bad
# it do
# is_expected.to be_truthy
# end
#
# # good
# it { is_expected.to be_truthy }
# it do
# expect(subject).to be_truthy
# end
#
# @example `EnforcedStyle: single_statement_only`
# # bad
# it do
# foo = 1
# is_expected.to be_truthy
# end
#
# # good
# it do
# foo = 1
# expect(subject).to be_truthy
# end
# it do
# is_expected.to be_truthy
# end
#
# @example `EnforcedStyle: disallow`
# # bad
# it { is_expected.to be_truthy }
#
# # good
# it { expect(subject).to be_truthy }
#
class ImplicitSubject < Base
extend AutoCorrector
include ConfigurableEnforcedStyle
MSG = "Don't use implicit subject."
RESTRICT_ON_SEND = %i[is_expected should should_not].freeze
# @!method implicit_subject?(node)
def_node_matcher :implicit_subject?, <<-PATTERN
(send nil? {:should :should_not :is_expected} ...)
PATTERN
def on_send(node)
return unless implicit_subject?(node)
return if valid_usage?(node)
add_offense(node) do |corrector|
autocorrect(corrector, node)
end
end
private
def autocorrect(corrector, node)
replacement = 'expect(subject)'
case node.method_name
when :should
replacement += '.to'
when :should_not
replacement += '.not_to'
end
corrector.replace(node.loc.selector, replacement)
end
def valid_usage?(node)
example = node.ancestors.find { |parent| example?(parent) }
return false if example.nil?
example.method?(:its) || allowed_by_style?(example)
end
def allowed_by_style?(example)
case style
when :single_line_only
example.single_line?
when :single_statement_only
!example.body.begin_type?
else
false
end
end
end
end
end
end

View File

@ -1,76 +0,0 @@
# frozen_string_literal: true
module RuboCop
module Cop
module RSpec
# Checks for explicitly referenced test subjects.
#
# RSpec lets you declare an "implicit subject" using `subject { ... }`
# which allows for tests like `it { is_expected.to be_valid }`.
# If you need to reference your test subject you should explicitly
# name it using `subject(:your_subject_name) { ... }`. Your test subjects
# should be the most important object in your tests so they deserve
# a descriptive name.
#
# This cop can be configured in your configuration using the
# `IgnoreSharedExamples` which will not report offenses for implicit
# subjects in shared example groups.
#
# @example
# # bad
# RSpec.describe User do
# subject { described_class.new }
#
# it 'is valid' do
# expect(subject.valid?).to be(true)
# end
# end
#
# # good
# RSpec.describe Foo do
# subject(:user) { described_class.new }
#
# it 'is valid' do
# expect(user.valid?).to be(true)
# end
# end
#
# # also good
# RSpec.describe Foo do
# subject(:user) { described_class.new }
#
# it { is_expected.to be_valid }
# end
#
class NamedSubject < Base
MSG = 'Name your test subject if you need to reference it explicitly.'
# @!method example_or_hook_block?(node)
def_node_matcher :example_or_hook_block?,
block_pattern('{#Examples.all #Hooks.all}')
# @!method shared_example?(node)
def_node_matcher :shared_example?,
block_pattern('#SharedGroups.examples')
# @!method subject_usage(node)
def_node_search :subject_usage, '$(send nil? :subject)'
def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler
if !example_or_hook_block?(node) || ignored_shared_example?(node)
return
end
subject_usage(node) do |subject_node|
add_offense(subject_node.loc.selector)
end
end
def ignored_shared_example?(node)
cop_config['IgnoreSharedExamples'] &&
node.each_ancestor(:block).any?(&method(:shared_example?))
end
end
end
end
end

View File

@ -1,17 +0,0 @@
# frozen_string_literal: true
module RuboCop
module RSpec
module FactoryBot
# Contains node matchers for common FactoryBot DSL.
module Language
extend RuboCop::NodePattern::Macros
# @!method factory_bot?(node)
def_node_matcher :factory_bot?, <<~PATTERN
(const {nil? cbase} {:FactoryGirl :FactoryBot})
PATTERN
end
end
end
end

View File

@ -12,8 +12,6 @@ RSpec:
- Expectations
- Helpers
- Hooks
- HookScopes
- Runners
- Subjects
ExampleGroups:
inherit_mode:
@ -81,12 +79,6 @@ RSpec:
- prepend_after
- after
- append_after
HookScopes:
- each
- example
- context
- all
- suite
Includes:
inherit_mode:
merge:
@ -98,10 +90,6 @@ RSpec:
- include_examples
Context:
- include_context
Runners:
- to
- to_not
- not_to
SharedGroups:
inherit_mode:
merge:
@ -194,7 +182,7 @@ RSpec/ChangeByZero:
Description: Prefer negated matchers over `to change.by(0)`.
Enabled: pending
VersionAdded: '2.11'
VersionChanged: '2.13'
VersionChanged: '2.14'
Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/ChangeByZero
NegatedMatcher: ~
@ -383,8 +371,10 @@ RSpec/ExampleWording:
have: has
HAVE: HAS
IgnoredWords: []
DisallowedExamples:
- works
VersionAdded: '1.0'
VersionChanged: '1.2'
VersionChanged: '2.13'
StyleGuide: https://rspec.rubystyle.guide/#should-in-example-docstrings
Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/ExampleWording
@ -492,8 +482,9 @@ RSpec/ImplicitSubject:
- single_line_only
- single_statement_only
- disallow
- require_implicit
VersionAdded: '1.29'
VersionChanged: '1.30'
VersionChanged: '2.13'
Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/ImplicitSubject
RSpec/InstanceSpy:
@ -621,8 +612,13 @@ RSpec/MultipleSubjects:
RSpec/NamedSubject:
Description: Checks for explicitly referenced test subjects.
Enabled: true
EnforcedStyle: always
SupportedStyles:
- always
- named_only
IgnoreSharedExamples: true
VersionAdded: 1.5.3
VersionChanged: '2.15'
StyleGuide: https://rspec.rubystyle.guide/#use-subject
Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/NamedSubject
@ -640,7 +636,11 @@ RSpec/NoExpectationExample:
Enabled: pending
Safe: false
VersionAdded: '2.13'
VersionChanged: '2.14'
Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/NoExpectationExample
AllowedPatterns:
- "^expect_"
- "^assert_"
RSpec/NotToNot:
Description: Checks for consistent method usage for negating expectations.
@ -763,6 +763,12 @@ RSpec/SingleArgumentMessageChain:
VersionChanged: '1.10'
Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/SingleArgumentMessageChain
RSpec/SortMetadata:
Description: Sort RSpec metadata alphabetically.
Enabled: pending
VersionAdded: '2.14'
Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/SortMetadata
RSpec/StubbedMock:
Description: Checks that message expectations do not have a configured response.
Enabled: true
@ -807,7 +813,6 @@ RSpec/VariableName:
- snake_case
- camelCase
AllowedPatterns: []
IgnoredPatterns: []
VersionAdded: '1.40'
VersionChanged: '2.13'
Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/VariableName
@ -866,6 +871,22 @@ RSpec/Capybara/FeatureMethods:
VersionChanged: '2.0'
Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/Capybara/FeatureMethods
RSpec/Capybara/NegationMatcher:
Description: Enforces use of `have_no_*` or `not_to` for negated expectations.
Enabled: pending
VersionAdded: '2.14'
EnforcedStyle: not_to
SupportedStyles:
- have_no
- not_to
Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/Capybara/NegationMatcher
RSpec/Capybara/SpecificActions:
Description: Checks for there is a more specific actions offered by Capybara.
Enabled: pending
VersionAdded: '2.14'
Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/Capybara/SpecificActions
RSpec/Capybara/SpecificFinders:
Description: Checks if there is a more specific finder offered by Capybara.
Enabled: pending
@ -901,6 +922,16 @@ RSpec/FactoryBot/AttributeDefinedStatically:
VersionChanged: '2.0'
Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/FactoryBot/AttributeDefinedStatically
RSpec/FactoryBot/ConsistentParenthesesStyle:
Description: Use a consistent style for parentheses in factory bot calls.
Enabled: pending
EnforcedStyle: require_parentheses
SupportedStyles:
- require_parentheses
- omit_parentheses
VersionAdded: '2.14'
Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/FactoryBot/ConsistentParenthesesStyle
RSpec/FactoryBot/CreateList:
Description: Checks for create_list usage.
Enabled: true
@ -954,6 +985,29 @@ RSpec/Rails/HaveHttpStatus:
VersionAdded: '2.12'
Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/Rails/HaveHttpStatus
RSpec/Rails/InferredSpecType:
Description: Identifies redundant spec type.
Enabled: pending
Safe: false
VersionAdded: '2.14'
Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/Rails/InferredSpecType
Inferences:
channels: channel
controllers: controller
features: feature
generator: generator
helpers: helper
jobs: job
mailboxes: mailbox
mailers: mailer
models: model
requests: request
integration: request
api: request
routing: routing
system: system
views: view
RSpec/Rails/HttpStatus:
Description: Enforces use of symbolic or numeric value to describe HTTP status.
Enabled: true

View File

@ -23,6 +23,8 @@ require_relative 'rubocop/cop/rspec/mixin/empty_line_separation'
require_relative 'rubocop/cop/rspec/mixin/inside_example_group'
require_relative 'rubocop/cop/rspec/mixin/namespace'
require_relative 'rubocop/cop/rspec/mixin/css_selector'
require_relative 'rubocop/cop/rspec/mixin/skip_or_pending'
require_relative 'rubocop/cop/rspec/mixin/capybara_help'
require_relative 'rubocop/rspec/concept'
require_relative 'rubocop/rspec/example_group'

View File

@ -0,0 +1,106 @@
# frozen_string_literal: true
module RuboCop
module Cop
module RSpec
module Capybara
# Enforces use of `have_no_*` or `not_to` for negated expectations.
#
# @example EnforcedStyle: not_to (default)
# # bad
# expect(page).to have_no_selector
# expect(page).to have_no_css('a')
#
# # good
# expect(page).not_to have_selector
# expect(page).not_to have_css('a')
#
# @example EnforcedStyle: have_no
# # bad
# expect(page).not_to have_selector
# expect(page).not_to have_css('a')
#
# # good
# expect(page).to have_no_selector
# expect(page).to have_no_css('a')
#
class NegationMatcher < Base
extend AutoCorrector
include ConfigurableEnforcedStyle
MSG = 'Use `expect(...).%<runner>s %<matcher>s`.'
CAPYBARA_MATCHERS = %w[
selector css xpath text title current_path link button
field checked_field unchecked_field select table
sibling ancestor
].freeze
POSITIVE_MATCHERS =
Set.new(CAPYBARA_MATCHERS) { |element| :"have_#{element}" }.freeze
NEGATIVE_MATCHERS =
Set.new(CAPYBARA_MATCHERS) { |element| :"have_no_#{element}" }
.freeze
RESTRICT_ON_SEND = (POSITIVE_MATCHERS + NEGATIVE_MATCHERS).freeze
# @!method not_to?(node)
def_node_matcher :not_to?, <<~PATTERN
(send ... :not_to
(send nil? %POSITIVE_MATCHERS ...))
PATTERN
# @!method have_no?(node)
def_node_matcher :have_no?, <<~PATTERN
(send ... :to
(send nil? %NEGATIVE_MATCHERS ...))
PATTERN
def on_send(node)
return unless offense?(node.parent)
matcher = node.method_name.to_s
add_offense(offense_range(node),
message: message(matcher)) do |corrector|
corrector.replace(node.parent.loc.selector, replaced_runner)
corrector.replace(node.loc.selector,
replaced_matcher(matcher))
end
end
private
def offense?(node)
(style == :have_no && not_to?(node)) ||
(style == :not_to && have_no?(node))
end
def offense_range(node)
node.parent.loc.selector.with(end_pos: node.loc.selector.end_pos)
end
def message(matcher)
format(MSG,
runner: replaced_runner,
matcher: replaced_matcher(matcher))
end
def replaced_runner
case style
when :have_no
'to'
when :not_to
'not_to'
end
end
def replaced_matcher(matcher)
case style
when :have_no
matcher.sub('have_', 'have_no_')
when :not_to
matcher.sub('have_no_', 'have_')
end
end
end
end
end
end
end

View File

@ -0,0 +1,85 @@
# frozen_string_literal: true
module RuboCop
module Cop
module RSpec
module Capybara
# Checks for there is a more specific actions offered by Capybara.
#
# @example
#
# # bad
# find('a').click
# find('button.cls').click
# find('a', exact_text: 'foo').click
# find('div button').click
#
# # good
# click_link
# click_button(class: 'cls')
# click_link(exact_text: 'foo')
# find('div').click_button
#
class SpecificActions < Base
MSG = "Prefer `%<good_action>s` over `find('%<selector>s').click`."
RESTRICT_ON_SEND = %i[click].freeze
SPECIFIC_ACTION = {
'button' => 'button',
'a' => 'link'
}.freeze
# @!method click_on_selector(node)
def_node_matcher :click_on_selector, <<-PATTERN
(send _ :find (str $_) ...)
PATTERN
def on_send(node)
click_on_selector(node.receiver) do |arg|
next unless supported_selector?(arg)
# Always check the last selector in the case of multiple selectors
# separated by whitespace.
# because the `.click` is executed on the element to
# which the last selector points.
next unless (selector = last_selector(arg))
next unless (action = specific_action(selector))
next unless CapybaraHelp.specific_option?(node.receiver, arg,
action)
next unless CapybaraHelp.specific_pseudo_classes?(arg)
range = offense_range(node, node.receiver)
add_offense(range, message: message(action, selector))
end
end
private
def specific_action(selector)
SPECIFIC_ACTION[last_selector(selector)]
end
def supported_selector?(selector)
!selector.match?(/[>,+~]/)
end
def last_selector(arg)
arg.split.last[/^\w+/, 0]
end
def offense_range(node, receiver)
receiver.loc.selector.with(end_pos: node.loc.expression.end_pos)
end
def message(action, selector)
format(MSG,
good_action: good_action(action),
selector: selector)
end
def good_action(action)
"click_#{action}"
end
end
end
end
end
end

View File

@ -0,0 +1,81 @@
# frozen_string_literal: true
module RuboCop
module Cop
module RSpec
module Capybara
# Checks for there is a more specific matcher offered by Capybara.
#
# @example
#
# # bad
# expect(page).to have_selector('button')
# expect(page).to have_no_selector('button.cls')
# expect(page).to have_css('button')
# expect(page).to have_no_css('a.cls', href: 'http://example.com')
# expect(page).to have_css('table.cls')
# expect(page).to have_css('select')
# expect(page).to have_css('input', exact_text: 'foo')
#
# # good
# expect(page).to have_button
# expect(page).to have_no_button(class: 'cls')
# expect(page).to have_button
# expect(page).to have_no_link('foo', class: 'cls', href: 'http://example.com')
# expect(page).to have_table(class: 'cls')
# expect(page).to have_select
# expect(page).to have_field('foo')
#
class SpecificMatcher < Base
include CapybaraHelp
MSG = 'Prefer `%<good_matcher>s` over `%<bad_matcher>s`.'
RESTRICT_ON_SEND = %i[have_selector have_no_selector have_css
have_no_css].freeze
SPECIFIC_MATCHER = {
'button' => 'button',
'a' => 'link',
'table' => 'table',
'select' => 'select',
'input' => 'field'
}.freeze
# @!method first_argument(node)
def_node_matcher :first_argument, <<-PATTERN
(send nil? _ (str $_) ... )
PATTERN
def on_send(node)
first_argument(node) do |arg|
next unless (matcher = specific_matcher(arg))
next if CssSelector.multiple_selectors?(arg)
next unless specific_option?(node, arg, matcher)
next unless specific_pseudo_classes?(arg)
add_offense(node, message: message(node, matcher))
end
end
private
def specific_matcher(arg)
splitted_arg = arg[/^\w+/, 0]
SPECIFIC_MATCHER[splitted_arg]
end
def message(node, matcher)
format(MSG,
good_matcher: good_matcher(node, matcher),
bad_matcher: node.method_name)
end
def good_matcher(node, matcher)
node.method_name
.to_s
.gsub(/selector|css/, matcher.to_s)
end
end
end
end
end
end

View File

@ -61,7 +61,7 @@ module RuboCop
extend AutoCorrector
MSG = 'Prefer `not_to change` over `to change.by(0)`.'
MSG_COMPOUND = 'Prefer %<preferred>s with compound expectations ' \
'over `change.by(0)`.'
'over `change.by(0)`.'
RESTRICT_ON_SEND = %i[change].freeze
# @!method expect_change_with_arguments(node)

View File

@ -43,7 +43,7 @@ module RuboCop
# # .rubocop.yml
# # RSpec/ContextWording:
# # AllowedPatterns:
# # - /とき$/
# # - とき$
#
# @example
# # bad
@ -92,7 +92,9 @@ module RuboCop
end
def expect_patterns
inspected = allowed_patterns.map(&:inspect)
inspected = allowed_patterns.map do |pattern|
pattern.inspect.gsub(/\A"|"\z/, '/')
end
return inspected.first if inspected.size == 1
inspected << "or #{inspected.pop}"

View File

@ -6,12 +6,17 @@ module RuboCop
# Checks for common mistakes in example descriptions.
#
# This cop will correct docstrings that begin with 'should' and 'it'.
# This cop will also look for insufficient examples and call them out.
#
# @see http://betterspecs.org/#should
#
# The autocorrect is experimental - use with care! It can be configured
# with CustomTransform (e.g. have => has) and IgnoredWords (e.g. only).
#
# Use the DisallowedExamples setting to prevent unclear or insufficient
# descriptions. Please note that this config will not be treated as
# case sensitive.
#
# @example
# # bad
# it 'should find nothing' do
@ -30,11 +35,21 @@ module RuboCop
# it 'does things' do
# end
#
# @example `DisallowedExamples: ['works']` (default)
# # bad
# it 'works' do
# end
#
# # good
# it 'marks the task as done' do
# end
class ExampleWording < Base
extend AutoCorrector
MSG_SHOULD = 'Do not use should when describing your tests.'
MSG_IT = "Do not repeat 'it' when describing your tests."
MSG_INSUFFICIENT_DESCRIPTION = 'Your example description is ' \
'insufficient.'
SHOULD_PREFIX = /\Ashould(?:n't)?\b/i.freeze
IT_PREFIX = /\Ait /i.freeze
@ -53,6 +68,9 @@ module RuboCop
add_wording_offense(description_node, MSG_SHOULD)
elsif message.match?(IT_PREFIX)
add_wording_offense(description_node, MSG_IT)
elsif insufficient_docstring?(description_node)
add_offense(docstring(description_node),
message: MSG_INSUFFICIENT_DESCRIPTION)
end
end
end
@ -113,6 +131,19 @@ module RuboCop
def ignored_words
cop_config.fetch('IgnoredWords', [])
end
def insufficient_docstring?(description_node)
insufficient_examples.include?(preprocess(text(description_node)))
end
def insufficient_examples
examples = cop_config.fetch('DisallowedExamples', [])
examples.map! { |example| preprocess(example) }
end
def preprocess(message)
message.strip.squeeze(' ').downcase
end
end
end
end

View File

@ -0,0 +1,115 @@
# frozen_string_literal: true
module RuboCop
module Cop
module RSpec
module FactoryBot
# Use a consistent style for parentheses in factory bot calls.
#
# @example
#
# # bad
# create :user
# build(:user)
# create(:login)
# create :login
#
# @example `EnforcedStyle: require_parentheses` (default)
#
# # good
# create(:user)
# create(:user)
# create(:login)
# build(:login)
#
# @example `EnforcedStyle: omit_parentheses`
#
# # good
# create :user
# build :user
# create :login
# create :login
#
# # also good
# # when method name and first argument are not on same line
# create(
# :user
# )
# build(
# :user,
# name: 'foo'
# )
#
class ConsistentParenthesesStyle < Base
extend AutoCorrector
include ConfigurableEnforcedStyle
include RuboCop::RSpec::FactoryBot::Language
include RuboCop::Cop::Util
def self.autocorrect_incompatible_with
[Style::MethodCallWithArgsParentheses]
end
MSG_REQUIRE_PARENS = 'Prefer method call with parentheses'
MSG_OMIT_PARENS = 'Prefer method call without parentheses'
FACTORY_CALLS = RuboCop::RSpec::FactoryBot::Language::METHODS
RESTRICT_ON_SEND = FACTORY_CALLS
# @!method factory_call(node)
def_node_matcher :factory_call, <<-PATTERN
(send
{#factory_bot? nil?} %FACTORY_CALLS
{sym str send lvar} _*
)
PATTERN
def on_send(node)
return if ambiguous_without_parentheses?(node)
factory_call(node) do
if node.parenthesized?
process_with_parentheses(node)
else
process_without_parentheses(node)
end
end
end
def process_with_parentheses(node)
return unless style == :omit_parentheses
return unless same_line?(node, node.first_argument)
add_offense(node.loc.selector,
message: MSG_OMIT_PARENS) do |corrector|
remove_parentheses(corrector, node)
end
end
def process_without_parentheses(node)
return unless style == :require_parentheses
add_offense(node.loc.selector,
message: MSG_REQUIRE_PARENS) do |corrector|
add_parentheses(node, corrector)
end
end
def ambiguous_without_parentheses?(node)
node.parent&.send_type? ||
node.parent&.pair_type? ||
node.parent&.array_type?
end
private
def remove_parentheses(corrector, node)
corrector.replace(node.location.begin, ' ')
corrector.remove(node.location.end)
end
end
end
end
end
end

View File

@ -238,8 +238,8 @@ module RuboCop
indent = ' ' * node.body.loc.column
indent_end = ' ' * node.parent.loc.column
" do #{node.arguments.source}\n" \
"#{indent}#{node.body.source}\n" \
"#{indent_end}end"
"#{indent}#{node.body.source}\n" \
"#{indent_end}end"
end
def format_singleline_block(node)

View File

@ -54,25 +54,7 @@ module RuboCop
MSG = 'Use `%<method>s` from `FactoryBot::Syntax::Methods`.'
RESTRICT_ON_SEND = %i[
attributes_for
attributes_for_list
attributes_for_pair
build
build_list
build_pair
build_stubbed
build_stubbed_list
build_stubbed_pair
create
create_list
create_pair
generate
generate_list
null
null_list
null_pair
].to_set.freeze
RESTRICT_ON_SEND = RuboCop::RSpec::FactoryBot::Language::METHODS
def on_send(node)
return unless factory_bot?(node.receiver)

View File

@ -76,8 +76,6 @@ module RuboCop
return unless top_level_groups.one?
example_group(node) do |send_node, example_group, arguments|
next if routing_spec?(arguments)
ensure_correct_file_path(send_node, example_group, arguments)
end
end
@ -85,7 +83,7 @@ module RuboCop
private
def ensure_correct_file_path(send_node, example_group, arguments)
pattern = pattern_for(example_group, arguments.first)
pattern = pattern_for(example_group, arguments)
return if filename_ends_with?(pattern)
# For the suffix shown in the offense message, modify the regular
@ -97,11 +95,13 @@ module RuboCop
end
def routing_spec?(args)
args.any?(&method(:routing_metadata?))
args.any?(&method(:routing_metadata?)) || routing_spec_path?
end
def pattern_for(example_group, method_name)
if spec_suffix_only? || !example_group.const_type?
def pattern_for(example_group, arguments)
method_name = arguments.first
if spec_suffix_only? || !example_group.const_type? ||
routing_spec?(arguments)
return pattern_for_spec_suffix_only
end
@ -149,8 +149,7 @@ module RuboCop
end
def filename_ends_with?(pattern)
filename = File.expand_path(processed_source.buffer.name)
filename.match?("#{pattern}$")
expanded_file_path.match?("#{pattern}$")
end
def relevant_rubocop_rspec_file?(_file)
@ -160,6 +159,14 @@ module RuboCop
def spec_suffix_only?
cop_config['SpecSuffixOnly']
end
def routing_spec_path?
expanded_file_path.include?('spec/routing/')
end
def expanded_file_path
File.expand_path(processed_source.buffer.name)
end
end
end
end

View File

@ -0,0 +1,167 @@
# frozen_string_literal: true
module RuboCop
module Cop
module RSpec
# Checks for usage of implicit subject (`is_expected` / `should`).
#
# This cop can be configured using the `EnforcedStyle` option
#
# @example `EnforcedStyle: single_line_only` (default)
# # bad
# it do
# is_expected.to be_truthy
# end
#
# # good
# it { is_expected.to be_truthy }
# it do
# expect(subject).to be_truthy
# end
#
# @example `EnforcedStyle: single_statement_only`
# # bad
# it do
# foo = 1
# is_expected.to be_truthy
# end
#
# # good
# it do
# foo = 1
# expect(subject).to be_truthy
# end
# it do
# is_expected.to be_truthy
# end
#
# @example `EnforcedStyle: disallow`
# # bad
# it { is_expected.to be_truthy }
#
# # good
# it { expect(subject).to be_truthy }
#
# @example `EnforcedStyle: require_implicit`
# # bad
# it { expect(subject).to be_truthy }
#
# # good
# it { is_expected.to be_truthy }
#
# # bad
# it do
# expect(subject).to be_truthy
# end
#
# # good
# it do
# is_expected.to be_truthy
# end
#
# # good
# it { expect(named_subject).to be_truthy }
#
class ImplicitSubject < Base
extend AutoCorrector
include ConfigurableEnforcedStyle
MSG_REQUIRE_EXPLICIT = "Don't use implicit subject."
MSG_REQUIRE_IMPLICIT = "Don't use explicit subject."
RESTRICT_ON_SEND = %i[
expect
is_expected
should
should_not
].freeze
# @!method explicit_unnamed_subject?(node)
def_node_matcher :explicit_unnamed_subject?, <<-PATTERN
(send nil? :expect (send nil? :subject))
PATTERN
# @!method implicit_subject?(node)
def_node_matcher :implicit_subject?, <<-PATTERN
(send nil? {:should :should_not :is_expected} ...)
PATTERN
def on_send(node)
return unless invalid?(node)
add_offense(node) do |corrector|
autocorrect(corrector, node)
end
end
private
def autocorrect(corrector, node)
case node.method_name
when :expect
corrector.replace(node, 'is_expected')
when :is_expected
corrector.replace(node.location.selector, 'expect(subject)')
when :should
corrector.replace(node.location.selector, 'expect(subject).to')
when :should_not
corrector.replace(node.location.selector, 'expect(subject).not_to')
end
end
def message(_node)
case style
when :require_implicit
MSG_REQUIRE_IMPLICIT
else
MSG_REQUIRE_EXPLICIT
end
end
def invalid?(node)
case style
when :require_implicit
explicit_unnamed_subject?(node)
when :disallow
implicit_subject_in_non_its?(node)
when :single_line_only
implicit_subject_in_non_its_and_non_single_line?(node)
when :single_statement_only
implicit_subject_in_non_its_and_non_single_statement?(node)
end
end
def implicit_subject_in_non_its?(node)
implicit_subject?(node) && !its?(node)
end
def implicit_subject_in_non_its_and_non_single_line?(node)
implicit_subject_in_non_its?(node) && !single_line?(node)
end
def implicit_subject_in_non_its_and_non_single_statement?(node)
implicit_subject_in_non_its?(node) && !single_statement?(node)
end
def its?(node)
example_of(node)&.method?(:its)
end
def single_line?(node)
example_of(node)&.single_line?
end
def single_statement?(node)
!example_of(node)&.body&.begin_type?
end
def example_of(node)
node.each_ancestor.find do |ancestor|
example?(ancestor)
end
end
end
end
end
end

View File

@ -43,6 +43,14 @@ module RuboCop
}
PATTERN
# @!method include_examples?(node)
def_node_matcher :include_examples?, <<~PATTERN
{
#{block_pattern(':include_examples')}
#{send_pattern(':include_examples')}
}
PATTERN
def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler
return unless example_group_with_body?(node)
@ -51,6 +59,10 @@ module RuboCop
private
def example_group_with_include_examples?(body)
body.children.any? { |sibling| include_examples?(sibling) }
end
def multiline_block?(block)
block.begin_type?
end
@ -59,11 +71,13 @@ module RuboCop
first_example = find_first_example(node)
return unless first_example
correct = !example_group_with_include_examples?(node)
first_example.right_siblings.each do |sibling|
next unless let?(sibling)
add_offense(sibling) do |corrector|
autocorrect(corrector, sibling, first_example)
autocorrect(corrector, sibling, first_example) if correct
end
end
end

View File

@ -0,0 +1,80 @@
# frozen_string_literal: true
module RuboCop
module Cop
module RSpec
# Help methods for capybara.
module CapybaraHelp
module_function
# @param node [RuboCop::AST::SendNode]
# @param locator [String]
# @param element [String]
# @return [Boolean]
def specific_option?(node, locator, element)
attrs = CssSelector.attributes(locator).keys
return false unless replaceable_element?(node, element, attrs)
attrs.all? do |attr|
CssSelector.specific_options?(element, attr)
end
end
# @param locator [String]
# @return [Boolean]
def specific_pseudo_classes?(locator)
CssSelector.pseudo_classes(locator).all? do |pseudo_class|
replaceable_pseudo_class?(pseudo_class, locator)
end
end
# @param pseudo_class [String]
# @param locator [String]
# @return [Boolean]
def replaceable_pseudo_class?(pseudo_class, locator)
return false unless CssSelector.specific_pesudo_classes?(pseudo_class)
case pseudo_class
when 'not()' then replaceable_pseudo_class_not?(locator)
else true
end
end
# @param locator [String]
# @return [Boolean]
def replaceable_pseudo_class_not?(locator)
locator.scan(/not\(.*?\)/).all? do |negation|
CssSelector.attributes(negation).values.all? do |v|
v.is_a?(TrueClass) || v.is_a?(FalseClass)
end
end
end
# @param node [RuboCop::AST::SendNode]
# @param element [String]
# @param attrs [Array<String>]
# @return [Boolean]
def replaceable_element?(node, element, attrs)
case element
when 'link' then replaceable_to_link?(node, attrs)
else true
end
end
# @param node [RuboCop::AST::SendNode]
# @param attrs [Array<String>]
# @return [Boolean]
def replaceable_to_link?(node, attrs)
include_option?(node, :href) || attrs.include?('href')
end
# @param node [RuboCop::AST::SendNode]
# @param option [Symbol]
# @return [Boolean]
def include_option?(node, option)
node.each_descendant(:sym).find { |opt| opt.value == option }
end
end
end
end
end

View File

@ -10,9 +10,56 @@ module RuboCop
id class style visible obscured exact exact_text normalize_ws match
wait filter_set focused
].freeze
SPECIFIC_OPTIONS = {
'button' => (
COMMON_OPTIONS + %w[disabled name value title type]
).freeze,
'link' => (
COMMON_OPTIONS + %w[href alt title download]
).freeze,
'table' => (
COMMON_OPTIONS + %w[
caption with_cols cols with_rows rows
]
).freeze,
'select' => (
COMMON_OPTIONS + %w[
disabled name placeholder options enabled_options
disabled_options selected with_selected multiple with_options
]
).freeze,
'field' => (
COMMON_OPTIONS + %w[
checked unchecked disabled valid name placeholder
validation_message readonly with type multiple
]
).freeze
}.freeze
SPECIFIC_PSEUDO_CLASSES = %w[
not() disabled enabled checked unchecked
].freeze
module_function
# @param element [String]
# @param attribute [String]
# @return [Boolean]
# @example
# specific_pesudo_classes?('button', 'name') # => true
# specific_pesudo_classes?('link', 'invalid') # => false
def specific_options?(element, attribute)
SPECIFIC_OPTIONS.fetch(element, []).include?(attribute)
end
# @param pseudo_class [String]
# @return [Boolean]
# @example
# specific_pesudo_classes?('disabled') # => true
# specific_pesudo_classes?('first-of-type') # => false
def specific_pesudo_classes?(pseudo_class)
SPECIFIC_PSEUDO_CLASSES.include?(pseudo_class)
end
# @param selector [String]
# @return [Boolean]
# @example
@ -75,7 +122,7 @@ module RuboCop
# multiple_selectors?('a.cls b#id') # => true
# multiple_selectors?('a.cls') # => false
def multiple_selectors?(selector)
selector.match?(/[ >,+]/)
selector.match?(/[ >,+~]/)
end
# @param value [String]

View File

@ -0,0 +1,23 @@
# frozen_string_literal: true
module RuboCop
module Cop
module RSpec
# Helps check offenses with variable definitions
module SkipOrPending
extend RuboCop::NodePattern::Macros
# @!method skipped_in_metadata?(node)
def_node_matcher :skipped_in_metadata?, <<-PATTERN
{
(send _ _ <#skip_or_pending? ...>)
(send _ _ ... (hash <(pair #skip_or_pending? { true str }) ...>))
}
PATTERN
# @!method skip_or_pending?(node)
def_node_matcher :skip_or_pending?, '{(sym :skip) (sym :pending)}'
end
end
end
end

View File

@ -0,0 +1,151 @@
# frozen_string_literal: true
module RuboCop
module Cop
module RSpec
# Checks for explicitly referenced test subjects.
#
# RSpec lets you declare an "implicit subject" using `subject { ... }`
# which allows for tests like `it { is_expected.to be_valid }`.
# If you need to reference your test subject you should explicitly
# name it using `subject(:your_subject_name) { ... }`. Your test subjects
# should be the most important object in your tests so they deserve
# a descriptive name.
#
# This cop can be configured in your configuration using `EnforcedStyle`,
# and `IgnoreSharedExamples` which will not report offenses for implicit
# subjects in shared example groups.
#
# @example `EnforcedStyle: always` (default)
# # bad
# RSpec.describe User do
# subject { described_class.new }
#
# it 'is valid' do
# expect(subject.valid?).to be(true)
# end
# end
#
# # good
# RSpec.describe User do
# subject(:user) { described_class.new }
#
# it 'is valid' do
# expect(user.valid?).to be(true)
# end
# end
#
# # also good
# RSpec.describe User do
# subject(:user) { described_class.new }
#
# it { is_expected.to be_valid }
# end
#
# @example `EnforcedStyle: named_only`
# # bad
# RSpec.describe User do
# subject(:user) { described_class.new }
#
# it 'is valid' do
# expect(subject.valid?).to be(true)
# end
# end
#
# # good
# RSpec.describe User do
# subject(:user) { described_class.new }
#
# it 'is valid' do
# expect(user.valid?).to be(true)
# end
# end
#
# # also good
# RSpec.describe User do
# subject { described_class.new }
#
# it { is_expected.to be_valid }
# end
#
# # acceptable
# RSpec.describe User do
# subject { described_class.new }
#
# it 'is valid' do
# expect(subject.valid?).to be(true)
# end
# end
class NamedSubject < Base
include ConfigurableEnforcedStyle
MSG = 'Name your test subject if you need to reference it explicitly.'
# @!method example_or_hook_block?(node)
def_node_matcher :example_or_hook_block?,
block_pattern('{#Examples.all #Hooks.all}')
# @!method shared_example?(node)
def_node_matcher :shared_example?,
block_pattern('#SharedGroups.examples')
# @!method subject_usage(node)
def_node_search :subject_usage, '$(send nil? :subject)'
def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler
if !example_or_hook_block?(node) || ignored_shared_example?(node)
return
end
subject_usage(node) do |subject_node|
check_explicit_subject(subject_node)
end
end
private
def ignored_shared_example?(node)
cop_config['IgnoreSharedExamples'] &&
node.each_ancestor(:block).any?(&method(:shared_example?))
end
def check_explicit_subject(node)
return if allow_explicit_subject?(node)
add_offense(node.loc.selector)
end
def allow_explicit_subject?(node)
!always? && !named_only?(node)
end
def always?
style == :always
end
def named_only?(node)
style == :named_only &&
subject_definition_is_named?(node)
end
def subject_definition_is_named?(node)
subject = nearest_subject(node)
subject&.send_node&.arguments?
end
def nearest_subject(node)
node
.each_ancestor(:block)
.lazy
.map { |block_node| find_subject(block_node) }
.find(&:itself)
end
def find_subject(block_node)
block_node.body.child_nodes.find { |send_node| subject?(send_node) }
end
end
end
end
end

View File

@ -133,7 +133,7 @@ module RuboCop
def count_up_nesting?(node, example_group)
example_group &&
(node.block_type? &&
!allowed_groups.include?(node.method_name))
!allowed_groups.include?(node.method_name.to_s))
end
def message(nesting)

View File

@ -28,7 +28,37 @@ module RuboCop
# expect(a?).to be(true)
# end
#
# This cop can be customized with an allowed expectation methods pattern
# with an `AllowedPatterns` option. ^expect_ and ^assert_ are allowed
# by default.
#
# @example `AllowedPatterns` configuration
#
# # .rubocop.yml
# # RSpec/NoExpectationExample:
# # AllowedPatterns:
# # - ^expect_
# # - ^assert_
#
# @example
# # bad
# it do
# not_expect_something
# end
#
# # good
# it do
# expect_something
# end
#
# it do
# assert_something
# end
#
class NoExpectationExample < Base
include AllowedPattern
include SkipOrPending
MSG = 'No expectation found in this example.'
# @!method regular_or_focused_example?(node)
@ -41,26 +71,29 @@ module RuboCop
}
PATTERN
# @!method including_any_expectation?(node)
# @!method includes_expectation?(node)
# @param [RuboCop::AST::Node] node
# @return [Boolean]
def_node_search(
:including_any_expectation?,
send_pattern('#Expectations.all')
)
def_node_search :includes_expectation?, <<~PATTERN
{
#{send_pattern('#Expectations.all')}
(send nil? `#matches_allowed_pattern? ...)
}
PATTERN
# @!method including_any_skip_example?(node)
# @!method includes_skip_example?(node)
# @param [RuboCop::AST::Node] node
# @return [Boolean]
def_node_search :including_any_skip_example?, <<~PATTERN
def_node_search :includes_skip_example?, <<~PATTERN
(send nil? {:pending :skip} ...)
PATTERN
# @param [RuboCop::AST::BlockNode] node
def on_block(node)
return unless regular_or_focused_example?(node)
return if including_any_expectation?(node)
return if including_any_skip_example?(node)
return if includes_expectation?(node)
return if includes_skip_example?(node)
return if skipped_in_metadata?(node.send_node)
add_offense(node)
end

View File

@ -33,6 +33,8 @@ module RuboCop
# end
#
class Pending < Base
include SkipOrPending
MSG = 'Pending spec found.'
# @!method skippable?(node)
@ -41,17 +43,6 @@ module RuboCop
{#ExampleGroups.regular #Examples.regular}
PATTERN
# @!method skipped_in_metadata?(node)
def_node_matcher :skipped_in_metadata?, <<-PATTERN
{
(send _ _ <#skip_or_pending? ...>)
(send _ _ ... (hash <(pair #skip_or_pending? { true str }) ...>))
}
PATTERN
# @!method skip_or_pending?(node)
def_node_matcher :skip_or_pending?, '{(sym :skip) (sym :pending)}'
# @!method pending_block?(node)
def_node_matcher :pending_block?,
send_pattern(<<~PATTERN)

Some files were not shown because too many files have changed in this diff Show More