brew vendor-gems: commit updates.
This commit is contained in:
parent
d4ff683691
commit
2be1c8cc78
@ -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-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-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-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/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/ruby-macho-3.0.0/lib")
|
||||||
$:.unshift File.expand_path("#{__dir__}/../#{RUBY_ENGINE}/#{Gem.ruby_api_version}/gems/simplecov-html-0.12.3/lib")
|
$:.unshift File.expand_path("#{__dir__}/../#{RUBY_ENGINE}/#{Gem.ruby_api_version}/gems/simplecov-html-0.12.3/lib")
|
||||||
|
|||||||
@ -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
|
|
||||||
@ -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
|
|
||||||
@ -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
|
|
||||||
@ -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
|
|
||||||
@ -12,8 +12,6 @@ RSpec:
|
|||||||
- Expectations
|
- Expectations
|
||||||
- Helpers
|
- Helpers
|
||||||
- Hooks
|
- Hooks
|
||||||
- HookScopes
|
|
||||||
- Runners
|
|
||||||
- Subjects
|
- Subjects
|
||||||
ExampleGroups:
|
ExampleGroups:
|
||||||
inherit_mode:
|
inherit_mode:
|
||||||
@ -81,12 +79,6 @@ RSpec:
|
|||||||
- prepend_after
|
- prepend_after
|
||||||
- after
|
- after
|
||||||
- append_after
|
- append_after
|
||||||
HookScopes:
|
|
||||||
- each
|
|
||||||
- example
|
|
||||||
- context
|
|
||||||
- all
|
|
||||||
- suite
|
|
||||||
Includes:
|
Includes:
|
||||||
inherit_mode:
|
inherit_mode:
|
||||||
merge:
|
merge:
|
||||||
@ -98,10 +90,6 @@ RSpec:
|
|||||||
- include_examples
|
- include_examples
|
||||||
Context:
|
Context:
|
||||||
- include_context
|
- include_context
|
||||||
Runners:
|
|
||||||
- to
|
|
||||||
- to_not
|
|
||||||
- not_to
|
|
||||||
SharedGroups:
|
SharedGroups:
|
||||||
inherit_mode:
|
inherit_mode:
|
||||||
merge:
|
merge:
|
||||||
@ -194,7 +182,7 @@ RSpec/ChangeByZero:
|
|||||||
Description: Prefer negated matchers over `to change.by(0)`.
|
Description: Prefer negated matchers over `to change.by(0)`.
|
||||||
Enabled: pending
|
Enabled: pending
|
||||||
VersionAdded: '2.11'
|
VersionAdded: '2.11'
|
||||||
VersionChanged: '2.13'
|
VersionChanged: '2.14'
|
||||||
Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/ChangeByZero
|
Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/ChangeByZero
|
||||||
NegatedMatcher: ~
|
NegatedMatcher: ~
|
||||||
|
|
||||||
@ -383,8 +371,10 @@ RSpec/ExampleWording:
|
|||||||
have: has
|
have: has
|
||||||
HAVE: HAS
|
HAVE: HAS
|
||||||
IgnoredWords: []
|
IgnoredWords: []
|
||||||
|
DisallowedExamples:
|
||||||
|
- works
|
||||||
VersionAdded: '1.0'
|
VersionAdded: '1.0'
|
||||||
VersionChanged: '1.2'
|
VersionChanged: '2.13'
|
||||||
StyleGuide: https://rspec.rubystyle.guide/#should-in-example-docstrings
|
StyleGuide: https://rspec.rubystyle.guide/#should-in-example-docstrings
|
||||||
Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/ExampleWording
|
Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/ExampleWording
|
||||||
|
|
||||||
@ -492,8 +482,9 @@ RSpec/ImplicitSubject:
|
|||||||
- single_line_only
|
- single_line_only
|
||||||
- single_statement_only
|
- single_statement_only
|
||||||
- disallow
|
- disallow
|
||||||
|
- require_implicit
|
||||||
VersionAdded: '1.29'
|
VersionAdded: '1.29'
|
||||||
VersionChanged: '1.30'
|
VersionChanged: '2.13'
|
||||||
Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/ImplicitSubject
|
Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/ImplicitSubject
|
||||||
|
|
||||||
RSpec/InstanceSpy:
|
RSpec/InstanceSpy:
|
||||||
@ -621,8 +612,13 @@ RSpec/MultipleSubjects:
|
|||||||
RSpec/NamedSubject:
|
RSpec/NamedSubject:
|
||||||
Description: Checks for explicitly referenced test subjects.
|
Description: Checks for explicitly referenced test subjects.
|
||||||
Enabled: true
|
Enabled: true
|
||||||
|
EnforcedStyle: always
|
||||||
|
SupportedStyles:
|
||||||
|
- always
|
||||||
|
- named_only
|
||||||
IgnoreSharedExamples: true
|
IgnoreSharedExamples: true
|
||||||
VersionAdded: 1.5.3
|
VersionAdded: 1.5.3
|
||||||
|
VersionChanged: '2.15'
|
||||||
StyleGuide: https://rspec.rubystyle.guide/#use-subject
|
StyleGuide: https://rspec.rubystyle.guide/#use-subject
|
||||||
Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/NamedSubject
|
Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/NamedSubject
|
||||||
|
|
||||||
@ -640,7 +636,11 @@ RSpec/NoExpectationExample:
|
|||||||
Enabled: pending
|
Enabled: pending
|
||||||
Safe: false
|
Safe: false
|
||||||
VersionAdded: '2.13'
|
VersionAdded: '2.13'
|
||||||
|
VersionChanged: '2.14'
|
||||||
Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/NoExpectationExample
|
Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/NoExpectationExample
|
||||||
|
AllowedPatterns:
|
||||||
|
- "^expect_"
|
||||||
|
- "^assert_"
|
||||||
|
|
||||||
RSpec/NotToNot:
|
RSpec/NotToNot:
|
||||||
Description: Checks for consistent method usage for negating expectations.
|
Description: Checks for consistent method usage for negating expectations.
|
||||||
@ -763,6 +763,12 @@ RSpec/SingleArgumentMessageChain:
|
|||||||
VersionChanged: '1.10'
|
VersionChanged: '1.10'
|
||||||
Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/SingleArgumentMessageChain
|
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:
|
RSpec/StubbedMock:
|
||||||
Description: Checks that message expectations do not have a configured response.
|
Description: Checks that message expectations do not have a configured response.
|
||||||
Enabled: true
|
Enabled: true
|
||||||
@ -807,7 +813,6 @@ RSpec/VariableName:
|
|||||||
- snake_case
|
- snake_case
|
||||||
- camelCase
|
- camelCase
|
||||||
AllowedPatterns: []
|
AllowedPatterns: []
|
||||||
IgnoredPatterns: []
|
|
||||||
VersionAdded: '1.40'
|
VersionAdded: '1.40'
|
||||||
VersionChanged: '2.13'
|
VersionChanged: '2.13'
|
||||||
Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/VariableName
|
Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/VariableName
|
||||||
@ -866,6 +871,22 @@ RSpec/Capybara/FeatureMethods:
|
|||||||
VersionChanged: '2.0'
|
VersionChanged: '2.0'
|
||||||
Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/Capybara/FeatureMethods
|
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:
|
RSpec/Capybara/SpecificFinders:
|
||||||
Description: Checks if there is a more specific finder offered by Capybara.
|
Description: Checks if there is a more specific finder offered by Capybara.
|
||||||
Enabled: pending
|
Enabled: pending
|
||||||
@ -901,6 +922,16 @@ RSpec/FactoryBot/AttributeDefinedStatically:
|
|||||||
VersionChanged: '2.0'
|
VersionChanged: '2.0'
|
||||||
Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/FactoryBot/AttributeDefinedStatically
|
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:
|
RSpec/FactoryBot/CreateList:
|
||||||
Description: Checks for create_list usage.
|
Description: Checks for create_list usage.
|
||||||
Enabled: true
|
Enabled: true
|
||||||
@ -954,6 +985,29 @@ RSpec/Rails/HaveHttpStatus:
|
|||||||
VersionAdded: '2.12'
|
VersionAdded: '2.12'
|
||||||
Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/Rails/HaveHttpStatus
|
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:
|
RSpec/Rails/HttpStatus:
|
||||||
Description: Enforces use of symbolic or numeric value to describe HTTP status.
|
Description: Enforces use of symbolic or numeric value to describe HTTP status.
|
||||||
Enabled: true
|
Enabled: true
|
||||||
@ -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/inside_example_group'
|
||||||
require_relative 'rubocop/cop/rspec/mixin/namespace'
|
require_relative 'rubocop/cop/rspec/mixin/namespace'
|
||||||
require_relative 'rubocop/cop/rspec/mixin/css_selector'
|
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/concept'
|
||||||
require_relative 'rubocop/rspec/example_group'
|
require_relative 'rubocop/rspec/example_group'
|
||||||
@ -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
|
||||||
@ -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
|
||||||
@ -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
|
||||||
@ -61,7 +61,7 @@ module RuboCop
|
|||||||
extend AutoCorrector
|
extend AutoCorrector
|
||||||
MSG = 'Prefer `not_to change` over `to change.by(0)`.'
|
MSG = 'Prefer `not_to change` over `to change.by(0)`.'
|
||||||
MSG_COMPOUND = 'Prefer %<preferred>s with compound expectations ' \
|
MSG_COMPOUND = 'Prefer %<preferred>s with compound expectations ' \
|
||||||
'over `change.by(0)`.'
|
'over `change.by(0)`.'
|
||||||
RESTRICT_ON_SEND = %i[change].freeze
|
RESTRICT_ON_SEND = %i[change].freeze
|
||||||
|
|
||||||
# @!method expect_change_with_arguments(node)
|
# @!method expect_change_with_arguments(node)
|
||||||
@ -43,7 +43,7 @@ module RuboCop
|
|||||||
# # .rubocop.yml
|
# # .rubocop.yml
|
||||||
# # RSpec/ContextWording:
|
# # RSpec/ContextWording:
|
||||||
# # AllowedPatterns:
|
# # AllowedPatterns:
|
||||||
# # - /とき$/
|
# # - とき$
|
||||||
#
|
#
|
||||||
# @example
|
# @example
|
||||||
# # bad
|
# # bad
|
||||||
@ -92,7 +92,9 @@ module RuboCop
|
|||||||
end
|
end
|
||||||
|
|
||||||
def expect_patterns
|
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
|
return inspected.first if inspected.size == 1
|
||||||
|
|
||||||
inspected << "or #{inspected.pop}"
|
inspected << "or #{inspected.pop}"
|
||||||
@ -6,12 +6,17 @@ module RuboCop
|
|||||||
# Checks for common mistakes in example descriptions.
|
# Checks for common mistakes in example descriptions.
|
||||||
#
|
#
|
||||||
# This cop will correct docstrings that begin with 'should' and 'it'.
|
# 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
|
# @see http://betterspecs.org/#should
|
||||||
#
|
#
|
||||||
# The autocorrect is experimental - use with care! It can be configured
|
# The autocorrect is experimental - use with care! It can be configured
|
||||||
# with CustomTransform (e.g. have => has) and IgnoredWords (e.g. only).
|
# 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
|
# @example
|
||||||
# # bad
|
# # bad
|
||||||
# it 'should find nothing' do
|
# it 'should find nothing' do
|
||||||
@ -30,11 +35,21 @@ module RuboCop
|
|||||||
# it 'does things' do
|
# it 'does things' do
|
||||||
# end
|
# end
|
||||||
#
|
#
|
||||||
|
# @example `DisallowedExamples: ['works']` (default)
|
||||||
|
# # bad
|
||||||
|
# it 'works' do
|
||||||
|
# end
|
||||||
|
#
|
||||||
|
# # good
|
||||||
|
# it 'marks the task as done' do
|
||||||
|
# end
|
||||||
class ExampleWording < Base
|
class ExampleWording < Base
|
||||||
extend AutoCorrector
|
extend AutoCorrector
|
||||||
|
|
||||||
MSG_SHOULD = 'Do not use should when describing your tests.'
|
MSG_SHOULD = 'Do not use should when describing your tests.'
|
||||||
MSG_IT = "Do not repeat 'it' 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
|
SHOULD_PREFIX = /\Ashould(?:n't)?\b/i.freeze
|
||||||
IT_PREFIX = /\Ait /i.freeze
|
IT_PREFIX = /\Ait /i.freeze
|
||||||
@ -53,6 +68,9 @@ module RuboCop
|
|||||||
add_wording_offense(description_node, MSG_SHOULD)
|
add_wording_offense(description_node, MSG_SHOULD)
|
||||||
elsif message.match?(IT_PREFIX)
|
elsif message.match?(IT_PREFIX)
|
||||||
add_wording_offense(description_node, MSG_IT)
|
add_wording_offense(description_node, MSG_IT)
|
||||||
|
elsif insufficient_docstring?(description_node)
|
||||||
|
add_offense(docstring(description_node),
|
||||||
|
message: MSG_INSUFFICIENT_DESCRIPTION)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@ -113,6 +131,19 @@ module RuboCop
|
|||||||
def ignored_words
|
def ignored_words
|
||||||
cop_config.fetch('IgnoredWords', [])
|
cop_config.fetch('IgnoredWords', [])
|
||||||
end
|
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
|
end
|
||||||
end
|
end
|
||||||
@ -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
|
||||||
@ -238,8 +238,8 @@ module RuboCop
|
|||||||
indent = ' ' * node.body.loc.column
|
indent = ' ' * node.body.loc.column
|
||||||
indent_end = ' ' * node.parent.loc.column
|
indent_end = ' ' * node.parent.loc.column
|
||||||
" do #{node.arguments.source}\n" \
|
" do #{node.arguments.source}\n" \
|
||||||
"#{indent}#{node.body.source}\n" \
|
"#{indent}#{node.body.source}\n" \
|
||||||
"#{indent_end}end"
|
"#{indent_end}end"
|
||||||
end
|
end
|
||||||
|
|
||||||
def format_singleline_block(node)
|
def format_singleline_block(node)
|
||||||
@ -54,25 +54,7 @@ module RuboCop
|
|||||||
|
|
||||||
MSG = 'Use `%<method>s` from `FactoryBot::Syntax::Methods`.'
|
MSG = 'Use `%<method>s` from `FactoryBot::Syntax::Methods`.'
|
||||||
|
|
||||||
RESTRICT_ON_SEND = %i[
|
RESTRICT_ON_SEND = RuboCop::RSpec::FactoryBot::Language::METHODS
|
||||||
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
|
|
||||||
|
|
||||||
def on_send(node)
|
def on_send(node)
|
||||||
return unless factory_bot?(node.receiver)
|
return unless factory_bot?(node.receiver)
|
||||||
@ -76,8 +76,6 @@ module RuboCop
|
|||||||
return unless top_level_groups.one?
|
return unless top_level_groups.one?
|
||||||
|
|
||||||
example_group(node) do |send_node, example_group, arguments|
|
example_group(node) do |send_node, example_group, arguments|
|
||||||
next if routing_spec?(arguments)
|
|
||||||
|
|
||||||
ensure_correct_file_path(send_node, example_group, arguments)
|
ensure_correct_file_path(send_node, example_group, arguments)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@ -85,7 +83,7 @@ module RuboCop
|
|||||||
private
|
private
|
||||||
|
|
||||||
def ensure_correct_file_path(send_node, example_group, arguments)
|
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)
|
return if filename_ends_with?(pattern)
|
||||||
|
|
||||||
# For the suffix shown in the offense message, modify the regular
|
# For the suffix shown in the offense message, modify the regular
|
||||||
@ -97,11 +95,13 @@ module RuboCop
|
|||||||
end
|
end
|
||||||
|
|
||||||
def routing_spec?(args)
|
def routing_spec?(args)
|
||||||
args.any?(&method(:routing_metadata?))
|
args.any?(&method(:routing_metadata?)) || routing_spec_path?
|
||||||
end
|
end
|
||||||
|
|
||||||
def pattern_for(example_group, method_name)
|
def pattern_for(example_group, arguments)
|
||||||
if spec_suffix_only? || !example_group.const_type?
|
method_name = arguments.first
|
||||||
|
if spec_suffix_only? || !example_group.const_type? ||
|
||||||
|
routing_spec?(arguments)
|
||||||
return pattern_for_spec_suffix_only
|
return pattern_for_spec_suffix_only
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -149,8 +149,7 @@ module RuboCop
|
|||||||
end
|
end
|
||||||
|
|
||||||
def filename_ends_with?(pattern)
|
def filename_ends_with?(pattern)
|
||||||
filename = File.expand_path(processed_source.buffer.name)
|
expanded_file_path.match?("#{pattern}$")
|
||||||
filename.match?("#{pattern}$")
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def relevant_rubocop_rspec_file?(_file)
|
def relevant_rubocop_rspec_file?(_file)
|
||||||
@ -160,6 +159,14 @@ module RuboCop
|
|||||||
def spec_suffix_only?
|
def spec_suffix_only?
|
||||||
cop_config['SpecSuffixOnly']
|
cop_config['SpecSuffixOnly']
|
||||||
end
|
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
|
end
|
||||||
end
|
end
|
||||||
@ -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
|
||||||
@ -43,6 +43,14 @@ module RuboCop
|
|||||||
}
|
}
|
||||||
PATTERN
|
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
|
def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler
|
||||||
return unless example_group_with_body?(node)
|
return unless example_group_with_body?(node)
|
||||||
|
|
||||||
@ -51,6 +59,10 @@ module RuboCop
|
|||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
|
def example_group_with_include_examples?(body)
|
||||||
|
body.children.any? { |sibling| include_examples?(sibling) }
|
||||||
|
end
|
||||||
|
|
||||||
def multiline_block?(block)
|
def multiline_block?(block)
|
||||||
block.begin_type?
|
block.begin_type?
|
||||||
end
|
end
|
||||||
@ -59,11 +71,13 @@ module RuboCop
|
|||||||
first_example = find_first_example(node)
|
first_example = find_first_example(node)
|
||||||
return unless first_example
|
return unless first_example
|
||||||
|
|
||||||
|
correct = !example_group_with_include_examples?(node)
|
||||||
|
|
||||||
first_example.right_siblings.each do |sibling|
|
first_example.right_siblings.each do |sibling|
|
||||||
next unless let?(sibling)
|
next unless let?(sibling)
|
||||||
|
|
||||||
add_offense(sibling) do |corrector|
|
add_offense(sibling) do |corrector|
|
||||||
autocorrect(corrector, sibling, first_example)
|
autocorrect(corrector, sibling, first_example) if correct
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@ -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
|
||||||
@ -10,9 +10,56 @@ module RuboCop
|
|||||||
id class style visible obscured exact exact_text normalize_ws match
|
id class style visible obscured exact exact_text normalize_ws match
|
||||||
wait filter_set focused
|
wait filter_set focused
|
||||||
].freeze
|
].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
|
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]
|
# @param selector [String]
|
||||||
# @return [Boolean]
|
# @return [Boolean]
|
||||||
# @example
|
# @example
|
||||||
@ -75,7 +122,7 @@ module RuboCop
|
|||||||
# multiple_selectors?('a.cls b#id') # => true
|
# multiple_selectors?('a.cls b#id') # => true
|
||||||
# multiple_selectors?('a.cls') # => false
|
# multiple_selectors?('a.cls') # => false
|
||||||
def multiple_selectors?(selector)
|
def multiple_selectors?(selector)
|
||||||
selector.match?(/[ >,+]/)
|
selector.match?(/[ >,+~]/)
|
||||||
end
|
end
|
||||||
|
|
||||||
# @param value [String]
|
# @param value [String]
|
||||||
@ -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
|
||||||
@ -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
|
||||||
@ -133,7 +133,7 @@ module RuboCop
|
|||||||
def count_up_nesting?(node, example_group)
|
def count_up_nesting?(node, example_group)
|
||||||
example_group &&
|
example_group &&
|
||||||
(node.block_type? &&
|
(node.block_type? &&
|
||||||
!allowed_groups.include?(node.method_name))
|
!allowed_groups.include?(node.method_name.to_s))
|
||||||
end
|
end
|
||||||
|
|
||||||
def message(nesting)
|
def message(nesting)
|
||||||
@ -28,7 +28,37 @@ module RuboCop
|
|||||||
# expect(a?).to be(true)
|
# expect(a?).to be(true)
|
||||||
# end
|
# 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
|
class NoExpectationExample < Base
|
||||||
|
include AllowedPattern
|
||||||
|
include SkipOrPending
|
||||||
|
|
||||||
MSG = 'No expectation found in this example.'
|
MSG = 'No expectation found in this example.'
|
||||||
|
|
||||||
# @!method regular_or_focused_example?(node)
|
# @!method regular_or_focused_example?(node)
|
||||||
@ -41,26 +71,29 @@ module RuboCop
|
|||||||
}
|
}
|
||||||
PATTERN
|
PATTERN
|
||||||
|
|
||||||
# @!method including_any_expectation?(node)
|
# @!method includes_expectation?(node)
|
||||||
# @param [RuboCop::AST::Node] node
|
# @param [RuboCop::AST::Node] node
|
||||||
# @return [Boolean]
|
# @return [Boolean]
|
||||||
def_node_search(
|
def_node_search :includes_expectation?, <<~PATTERN
|
||||||
:including_any_expectation?,
|
{
|
||||||
send_pattern('#Expectations.all')
|
#{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
|
# @param [RuboCop::AST::Node] node
|
||||||
# @return [Boolean]
|
# @return [Boolean]
|
||||||
def_node_search :including_any_skip_example?, <<~PATTERN
|
def_node_search :includes_skip_example?, <<~PATTERN
|
||||||
(send nil? {:pending :skip} ...)
|
(send nil? {:pending :skip} ...)
|
||||||
PATTERN
|
PATTERN
|
||||||
|
|
||||||
# @param [RuboCop::AST::BlockNode] node
|
# @param [RuboCop::AST::BlockNode] node
|
||||||
def on_block(node)
|
def on_block(node)
|
||||||
return unless regular_or_focused_example?(node)
|
return unless regular_or_focused_example?(node)
|
||||||
return if including_any_expectation?(node)
|
return if includes_expectation?(node)
|
||||||
return if including_any_skip_example?(node)
|
return if includes_skip_example?(node)
|
||||||
|
return if skipped_in_metadata?(node.send_node)
|
||||||
|
|
||||||
add_offense(node)
|
add_offense(node)
|
||||||
end
|
end
|
||||||
@ -33,6 +33,8 @@ module RuboCop
|
|||||||
# end
|
# end
|
||||||
#
|
#
|
||||||
class Pending < Base
|
class Pending < Base
|
||||||
|
include SkipOrPending
|
||||||
|
|
||||||
MSG = 'Pending spec found.'
|
MSG = 'Pending spec found.'
|
||||||
|
|
||||||
# @!method skippable?(node)
|
# @!method skippable?(node)
|
||||||
@ -41,17 +43,6 @@ module RuboCop
|
|||||||
{#ExampleGroups.regular #Examples.regular}
|
{#ExampleGroups.regular #Examples.regular}
|
||||||
PATTERN
|
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)
|
# @!method pending_block?(node)
|
||||||
def_node_matcher :pending_block?,
|
def_node_matcher :pending_block?,
|
||||||
send_pattern(<<~PATTERN)
|
send_pattern(<<~PATTERN)
|
||||||
@ -0,0 +1,145 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module RuboCop
|
||||||
|
module Cop
|
||||||
|
module RSpec
|
||||||
|
module Rails
|
||||||
|
# Identifies redundant spec type.
|
||||||
|
#
|
||||||
|
# After setting up rspec-rails, you will have enabled
|
||||||
|
# `config.infer_spec_type_from_file_location!` by default in
|
||||||
|
# spec/rails_helper.rb. This cop works in conjunction with this config.
|
||||||
|
# If you disable this config, disable this cop as well.
|
||||||
|
#
|
||||||
|
# @safety
|
||||||
|
# This cop is marked as unsafe because
|
||||||
|
# `config.infer_spec_type_from_file_location!` may not be enabled.
|
||||||
|
#
|
||||||
|
# @example
|
||||||
|
# # bad
|
||||||
|
# # spec/models/user_spec.rb
|
||||||
|
# RSpec.describe User, type: :model do
|
||||||
|
# end
|
||||||
|
#
|
||||||
|
# # good
|
||||||
|
# # spec/models/user_spec.rb
|
||||||
|
# RSpec.describe User do
|
||||||
|
# end
|
||||||
|
#
|
||||||
|
# # good
|
||||||
|
# # spec/models/user_spec.rb
|
||||||
|
# RSpec.describe User, type: :common do
|
||||||
|
# end
|
||||||
|
#
|
||||||
|
# @example `Inferences` configuration
|
||||||
|
# # .rubocop.yml
|
||||||
|
# # RSpec/InferredSpecType:
|
||||||
|
# # Inferences:
|
||||||
|
# # services: service
|
||||||
|
#
|
||||||
|
# # bad
|
||||||
|
# # spec/services/user_spec.rb
|
||||||
|
# RSpec.describe User, type: :service do
|
||||||
|
# end
|
||||||
|
#
|
||||||
|
# # good
|
||||||
|
# # spec/services/user_spec.rb
|
||||||
|
# RSpec.describe User do
|
||||||
|
# end
|
||||||
|
#
|
||||||
|
# # good
|
||||||
|
# # spec/services/user_spec.rb
|
||||||
|
# RSpec.describe User, type: :common do
|
||||||
|
# end
|
||||||
|
class InferredSpecType < Base
|
||||||
|
extend AutoCorrector
|
||||||
|
|
||||||
|
MSG = 'Remove redundant spec type.'
|
||||||
|
|
||||||
|
# @param [RuboCop::AST::BlockNode] node
|
||||||
|
def on_block(node)
|
||||||
|
return unless example_group?(node)
|
||||||
|
|
||||||
|
pair_node = describe_with_type(node)
|
||||||
|
return unless pair_node
|
||||||
|
return unless inferred_type?(pair_node)
|
||||||
|
|
||||||
|
removable_node = detect_removable_node(pair_node)
|
||||||
|
add_offense(removable_node) do |corrector|
|
||||||
|
autocorrect(corrector, removable_node)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
alias on_numblock on_block
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
# @!method describe_with_type(node)
|
||||||
|
# @param [RuboCop::AST::BlockNode] node
|
||||||
|
# @return [RuboCop::AST::PairNode, nil]
|
||||||
|
def_node_matcher :describe_with_type, <<~PATTERN
|
||||||
|
(block
|
||||||
|
(send #rspec? #ExampleGroups.all
|
||||||
|
...
|
||||||
|
(hash <$(pair (sym :type) sym) ...>)
|
||||||
|
)
|
||||||
|
...
|
||||||
|
)
|
||||||
|
PATTERN
|
||||||
|
|
||||||
|
# @param [RuboCop::AST::Corrector] corrector
|
||||||
|
# @param [RuboCop::AST::Node] node
|
||||||
|
def autocorrect(corrector, node)
|
||||||
|
corrector.remove(remove_range(node))
|
||||||
|
end
|
||||||
|
|
||||||
|
# @param [RuboCop::AST::Node] node
|
||||||
|
# @return [Parser::Source::Range]
|
||||||
|
def remove_range(node)
|
||||||
|
if node.left_sibling
|
||||||
|
node.loc.expression.with(
|
||||||
|
begin_pos: node.left_sibling.loc.expression.end_pos
|
||||||
|
)
|
||||||
|
elsif node.right_sibling
|
||||||
|
node.loc.expression.with(
|
||||||
|
end_pos: node.right_sibling.loc.expression.begin_pos
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# @param [RuboCop::AST::PairNode] node
|
||||||
|
# @return [RuboCop::AST::Node]
|
||||||
|
def detect_removable_node(node)
|
||||||
|
if node.parent.pairs.size == 1
|
||||||
|
node.parent
|
||||||
|
else
|
||||||
|
node
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# @return [String]
|
||||||
|
def file_path
|
||||||
|
processed_source.file_path
|
||||||
|
end
|
||||||
|
|
||||||
|
# @param [RuboCop::AST::PairNode] node
|
||||||
|
# @return [Boolean]
|
||||||
|
def inferred_type?(node)
|
||||||
|
inferred_type_from_file_path.inspect == node.value.source
|
||||||
|
end
|
||||||
|
|
||||||
|
# @return [Symbol, nil]
|
||||||
|
def inferred_type_from_file_path
|
||||||
|
inferences.find do |prefix, type|
|
||||||
|
break type.to_sym if file_path.include?("spec/#{prefix}/")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# @return [Hash]
|
||||||
|
def inferences
|
||||||
|
cop_config['Inferences'] || {}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user