Skip to content

Commit

Permalink
Implement dynamic matchers
Browse files Browse the repository at this point in the history
  • Loading branch information
sl4vr authored and pirj committed Nov 3, 2020
1 parent 2cd7639 commit e515d21
Show file tree
Hide file tree
Showing 52 changed files with 487 additions and 308 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Expand Up @@ -5,6 +5,7 @@
* Remove deprecated class `::RuboCop::Cop::RSpec::Cop`. ([@bquorning][])
* Retire `RSpec/InvalidPredicateMatcher` cop. ([@pirj][])
* Remove the code responsible for filtering files to inspect. ([@pirj][])
* Make RSpec language elements configurable. ([@sl4vr][])

## 2.0.0.pre (2020-10-22)

Expand Down Expand Up @@ -580,3 +581,4 @@ Compatibility release so users can upgrade RuboCop to 0.51.0. No new features.
[@koic]: https://github.com/koic
[@Rafix02]: https://github.com/Rafix02
[@PhilCoggins]: https://github.com/PhilCoggins
[@sl4vr]: https://github.com/sl4vr
77 changes: 77 additions & 0 deletions config/default.yml
Expand Up @@ -3,6 +3,83 @@ RSpec:
Include:
- "**/*_spec.rb"
- "**/spec/**/*"
Language:
ExampleGroups:
Regular:
- describe
- context
- feature
- example_group
Skipped:
- xdescribe
- xcontext
- xfeature
Focused:
- fdescribe
- fcontext
- ffeature
Examples:
Regular:
- it
- specify
- example
- scenario
- its
Focused:
- fit
- fspecify
- fexample
- fscenario
- focus
Skipped:
- xit
- xspecify
- xexample
- xscenario
- skip
Pending:
- pending
Expectations:
- expect
- is_expected
- expect_any_instance_of
Helpers:
- let
- let!
Hooks:
- prepend_before
- before
- append_before
- around
- prepend_after
- after
- append_after
HookScopes:
- each
- example
- context
- all
- suite
Includes:
Examples:
- it_behaves_like
- it_should_behave_like
- include_examples
Context:
- include_context
Runners:
- to
- to_not
- not_to
SharedGroups:
Examples:
- shared_examples
- shared_examples_for
Context:
- shared_context
Subjects:
- subject
- subject!

RSpec/AlignLeftLetBrace:
Description: Checks that left braces for adjacent single line lets are aligned.
Expand Down
3 changes: 1 addition & 2 deletions lib/rubocop-rspec.rb
Expand Up @@ -5,13 +5,12 @@

require 'rubocop'

require_relative 'rubocop/rspec'
require_relative 'rubocop/rspec/version'
require_relative 'rubocop/rspec/inject'
require_relative 'rubocop/rspec/node'
require_relative 'rubocop/rspec/wording'
require_relative 'rubocop/rspec/language'
require_relative 'rubocop/rspec/language/node_pattern'
require_relative 'rubocop/rspec/language'

require_relative 'rubocop/cop/rspec/mixin/top_level_group'
require_relative 'rubocop/cop/rspec/mixin/variable'
Expand Down
10 changes: 7 additions & 3 deletions lib/rubocop/cop/rspec/align_left_let_brace.rb
Expand Up @@ -27,11 +27,9 @@ def self.autocorrect_incompatible_with
end

def on_new_investigation
super
return if processed_source.blank?

token_aligner =
RuboCop::RSpec::AlignLetBrace.new(processed_source.ast, :begin)

token_aligner.offending_tokens.each do |let|
add_offense(let.loc.begin) do |corrector|
corrector.insert_before(
Expand All @@ -40,6 +38,12 @@ def on_new_investigation
end
end
end

private

def token_aligner
RuboCop::RSpec::AlignLetBrace.new(processed_source.ast, :begin)
end
end
end
end
Expand Down
10 changes: 7 additions & 3 deletions lib/rubocop/cop/rspec/align_right_let_brace.rb
Expand Up @@ -27,11 +27,9 @@ def self.autocorrect_incompatible_with
end

def on_new_investigation
super
return if processed_source.blank?

token_aligner =
RuboCop::RSpec::AlignLetBrace.new(processed_source.ast, :end)

token_aligner.offending_tokens.each do |let|
add_offense(let.loc.end) do |corrector|
corrector.insert_before(
Expand All @@ -40,6 +38,12 @@ def on_new_investigation
end
end
end

private

def token_aligner
RuboCop::RSpec::AlignLetBrace.new(processed_source.ast, :end)
end
end
end
end
Expand Down
9 changes: 8 additions & 1 deletion lib/rubocop/cop/rspec/base.rb
Expand Up @@ -6,14 +6,21 @@ module RSpec
# @abstract parent class to RSpec cops
class Base < ::RuboCop::Cop::Base
include RuboCop::RSpec::Language
include RuboCop::RSpec::Language::NodePattern
extend RuboCop::RSpec::Language::NodePattern

exclude_from_registry

# Invoke the original inherited hook so our cops are recognized
def self.inherited(subclass) # rubocop:disable Lint/MissingSuper
RuboCop::Cop::Base.inherited(subclass)
end

# Set the config for dynamic DSL configuration-aware helpers
# that have no other means of accessing the configuration.
def on_new_investigation
super
RuboCop::RSpec::Language.config = config['RSpec']['Language']
end
end
end
end
Expand Down
2 changes: 1 addition & 1 deletion lib/rubocop/cop/rspec/be.rb
Expand Up @@ -23,7 +23,7 @@ class Be < Base
MSG = 'Don\'t use `be` without an argument.'

def_node_matcher :be_without_args, <<-PATTERN
(send _ #{Runners::ALL.node_pattern_union} $(send nil? :be))
(send _ #Runners.all $(send nil? :be))
PATTERN

def on_send(node)
Expand Down
4 changes: 2 additions & 2 deletions lib/rubocop/cop/rspec/capybara/current_path_expectation.rb
Expand Up @@ -37,13 +37,13 @@ class CurrentPathExpectation < Base
# Supported matchers: eq(...) / match(/regexp/) / match('regexp')
def_node_matcher :as_is_matcher, <<-PATTERN
(send
#expectation_set_on_current_path $#{Runners::ALL.node_pattern_union}
#expectation_set_on_current_path $#Runners.all
${(send nil? :eq ...) (send nil? :match (regexp ...))})
PATTERN

def_node_matcher :regexp_str_matcher, <<-PATTERN
(send
#expectation_set_on_current_path $#{Runners::ALL.node_pattern_union}
#expectation_set_on_current_path $#Runners.all
$(send nil? :match (str $_)))
PATTERN

Expand Down
5 changes: 3 additions & 2 deletions lib/rubocop/cop/rspec/capybara/feature_methods.rb
Expand Up @@ -55,8 +55,9 @@ class FeatureMethods < Base
feature: :describe
}.freeze

def_node_matcher :capybara_speak,
SelectorSet.new(MAP.keys).node_pattern_union
def_node_matcher :capybara_speak, <<-PATTERN
{#{MAP.keys.map(&:inspect).join(' ')}}
PATTERN

def_node_matcher :spec?, <<-PATTERN
(block
Expand Down
3 changes: 1 addition & 2 deletions lib/rubocop/cop/rspec/described_class.rb
Expand Up @@ -65,8 +65,7 @@ class DescribedClass < Base
(block (send (const nil? {:Class :Module :Struct}) :new ...) ...)
PATTERN

def_node_matcher :rspec_block?,
RuboCop::RSpec::Language::ALL.block_pattern
def_node_matcher :rspec_block?, block_pattern('#ALL.all')

def_node_matcher :scope_changing_syntax?, '{def class module}'

Expand Down
3 changes: 1 addition & 2 deletions lib/rubocop/cop/rspec/described_class_module_wrapping.rb
Expand Up @@ -22,8 +22,7 @@ module RSpec
class DescribedClassModuleWrapping < Base
MSG = 'Avoid opening modules and defining specs within them.'

def_node_search :find_rspec_blocks,
ExampleGroups::ALL.block_pattern
def_node_search :find_rspec_blocks, block_pattern('#ExampleGroups.all')

def on_module(node)
find_rspec_blocks(node) do
Expand Down
2 changes: 1 addition & 1 deletion lib/rubocop/cop/rspec/dialect.rb
Expand Up @@ -47,7 +47,7 @@ class Dialect < Base

MSG = 'Prefer `%<prefer>s` over `%<current>s`.'

def_node_matcher :rspec_method?, ALL.send_pattern
def_node_matcher :rspec_method?, send_pattern('#ALL.all')

def on_send(node)
return unless rspec_method?(node)
Expand Down
13 changes: 6 additions & 7 deletions lib/rubocop/cop/rspec/empty_example_group.rb
Expand Up @@ -76,7 +76,7 @@ class EmptyExampleGroup < Base
# @param node [RuboCop::AST::Node]
# @yield [RuboCop::AST::Node] example group body
def_node_matcher :example_group_body, <<~PATTERN
(block #{ExampleGroups::ALL.send_pattern} args $_)
(block #{send_pattern('#ExampleGroups.all')} args $_)
PATTERN

# @!method example_or_group_or_include?(node)
Expand All @@ -95,11 +95,10 @@ class EmptyExampleGroup < Base
# @return [Array<RuboCop::AST::Node>] matching nodes
def_node_matcher :example_or_group_or_include?, <<~PATTERN
{
#{Examples::ALL.send_pattern}
#{Examples::ALL.block_pattern}
#{ExampleGroups::ALL.block_pattern}
#{Includes::ALL.send_pattern}
#{Includes::ALL.block_pattern}
#{block_pattern(
'{#Examples.all #ExampleGroups.all #Includes.all}'
)}
#{send_pattern('{#Examples.all #Includes.all}')}
(send nil? #custom_include? ...)
}
PATTERN
Expand All @@ -120,7 +119,7 @@ class EmptyExampleGroup < Base
# @param node [RuboCop::AST::Node]
# @return [Array<RuboCop::AST::Node>] matching nodes
def_node_matcher :examples_inside_block?, <<~PATTERN
(block !#{Hooks::ALL.send_pattern} _ #examples?)
(block !#{send_pattern('#Hooks.all')} _ #examples?)
PATTERN

# @!method examples_directly_or_in_block?(node)
Expand Down
2 changes: 1 addition & 1 deletion lib/rubocop/cop/rspec/empty_hook.rb
Expand Up @@ -29,7 +29,7 @@ class EmptyHook < Base
MSG = 'Empty hook detected.'

def_node_matcher :empty_hook?, <<~PATTERN
(block $#{Hooks::ALL.send_pattern} _ nil?)
(block $#{send_pattern('#Hooks.all')} _ nil?)
PATTERN

def on_block(node)
Expand Down
2 changes: 1 addition & 1 deletion lib/rubocop/cop/rspec/empty_line_after_subject.rb
Expand Up @@ -32,7 +32,7 @@ def on_block(node)

def in_spec_block?(node)
node.each_ancestor(:block).any? do |ancestor|
Examples::ALL.include?(ancestor.method_name)
Examples.all(ancestor.method_name)
end
end
end
Expand Down
2 changes: 1 addition & 1 deletion lib/rubocop/cop/rspec/expect_actual.rb
Expand Up @@ -48,7 +48,7 @@ class ExpectActual < Base
def_node_matcher :expect_literal, <<~PATTERN
(send
(send nil? :expect $#literal?)
#{Runners::ALL.node_pattern_union}
#Runners.all
{
(send (send nil? $:be) :== $_)
(send nil? $_ $_ ...)
Expand Down
2 changes: 1 addition & 1 deletion lib/rubocop/cop/rspec/expect_in_hook.rb
Expand Up @@ -23,7 +23,7 @@ module RSpec
class ExpectInHook < Base
MSG = 'Do not use `%<expect>s` in `%<hook>s` hook'

def_node_search :expectation, Expectations::ALL.send_pattern
def_node_search :expectation, send_pattern('#Expectations.all')

def on_block(node)
return unless hook?(node)
Expand Down
20 changes: 13 additions & 7 deletions lib/rubocop/cop/rspec/focus.rb
Expand Up @@ -22,19 +22,25 @@ module RSpec
class Focus < Base
MSG = 'Focused spec found.'

focused = ExampleGroups::FOCUSED + Examples::FOCUSED

def_node_matcher :focusable_selector?,
(ExampleGroups::GROUPS + ExampleGroups::SKIPPED +
Examples::EXAMPLES + Examples::SKIPPED +
Examples::PENDING).node_pattern_union
def_node_matcher :focusable_selector?, <<-PATTERN
{
#ExampleGroups.regular
#ExampleGroups.skipped
#Examples.regular
#Examples.skipped
#Examples.pending
}
PATTERN

def_node_matcher :metadata, <<-PATTERN
{(send #rspec? #focusable_selector? <$(sym :focus) ...>)
(send #rspec? #focusable_selector? ... (hash <$(pair (sym :focus) true) ...>))}
PATTERN

def_node_matcher :focused_block?, focused.send_pattern
def_node_matcher :focused_block?,
send_pattern(<<~PATTERN)
{#ExampleGroups.focused #Examples.focused}
PATTERN

def on_send(node)
focus_metadata(node) do |focus|
Expand Down
6 changes: 2 additions & 4 deletions lib/rubocop/cop/rspec/hook_argument.rb
Expand Up @@ -64,13 +64,11 @@ class HookArgument < Base
IMPLICIT_MSG = 'Omit the default `%<scope>p` argument for RSpec hooks.'
EXPLICIT_MSG = 'Use `%<scope>p` for RSpec hooks.'

def_node_matcher :hook?, Hooks::ALL.node_pattern_union

def_node_matcher :scoped_hook, <<-PATTERN
(block $(send _ #hook? (sym ${:each :example})) ...)
(block $(send _ #Hooks.all (sym ${:each :example})) ...)
PATTERN

def_node_matcher :unscoped_hook, '(block $(send _ #hook?) ...)'
def_node_matcher :unscoped_hook, '(block $(send _ #Hooks.all) ...)'

def on_block(node)
hook(node) do |method_send, scope_name|
Expand Down
4 changes: 2 additions & 2 deletions lib/rubocop/cop/rspec/hooks_before_examples.rb
Expand Up @@ -30,8 +30,8 @@ class HooksBeforeExamples < Base

def_node_matcher :example_or_group?, <<-PATTERN
{
#{(Examples::ALL + ExampleGroups::ALL).block_pattern}
#{Includes::EXAMPLES.send_pattern}
#{block_pattern('{#ExampleGroups.all #Examples.all}')}
#{send_pattern('#Includes.examples')}
}
PATTERN

Expand Down
2 changes: 1 addition & 1 deletion lib/rubocop/cop/rspec/implicit_expect.rb
Expand Up @@ -33,7 +33,7 @@ class ImplicitExpect < Base
def_node_matcher :implicit_expect, <<-PATTERN
{
(send nil? ${:should :should_not} ...)
(send (send nil? $:is_expected) #{Runners::ALL.node_pattern_union} ...)
(send (send nil? $:is_expected) #Runners.all ...)
}
PATTERN

Expand Down

0 comments on commit e515d21

Please sign in to comment.