From e515d215e82b5abd12b64134dbf8728b5431002e Mon Sep 17 00:00:00 2001 From: Viacheslav Mefodin Date: Tue, 18 Aug 2020 12:20:49 +0300 Subject: [PATCH] Implement dynamic matchers --- CHANGELOG.md | 2 + config/default.yml | 77 ++++++ lib/rubocop-rspec.rb | 3 +- lib/rubocop/cop/rspec/align_left_let_brace.rb | 10 +- .../cop/rspec/align_right_let_brace.rb | 10 +- lib/rubocop/cop/rspec/base.rb | 9 +- lib/rubocop/cop/rspec/be.rb | 2 +- .../capybara/current_path_expectation.rb | 4 +- .../cop/rspec/capybara/feature_methods.rb | 5 +- lib/rubocop/cop/rspec/described_class.rb | 3 +- .../rspec/described_class_module_wrapping.rb | 3 +- lib/rubocop/cop/rspec/dialect.rb | 2 +- lib/rubocop/cop/rspec/empty_example_group.rb | 13 +- lib/rubocop/cop/rspec/empty_hook.rb | 2 +- .../cop/rspec/empty_line_after_subject.rb | 2 +- lib/rubocop/cop/rspec/expect_actual.rb | 2 +- lib/rubocop/cop/rspec/expect_in_hook.rb | 2 +- lib/rubocop/cop/rspec/focus.rb | 20 +- lib/rubocop/cop/rspec/hook_argument.rb | 6 +- .../cop/rspec/hooks_before_examples.rb | 4 +- lib/rubocop/cop/rspec/implicit_expect.rb | 2 +- lib/rubocop/cop/rspec/let_before_examples.rb | 4 +- lib/rubocop/cop/rspec/let_setup.rb | 11 +- lib/rubocop/cop/rspec/message_spies.rb | 2 +- .../cop/rspec/mixin/top_level_group.rb | 9 +- lib/rubocop/cop/rspec/mixin/variable.rb | 6 +- .../cop/rspec/multiple_expectations.rb | 2 +- .../cop/rspec/multiple_memoized_helpers.rb | 2 + lib/rubocop/cop/rspec/named_subject.rb | 20 +- lib/rubocop/cop/rspec/overwriting_setup.rb | 3 +- lib/rubocop/cop/rspec/pending.rb | 18 +- lib/rubocop/cop/rspec/predicate_matcher.rb | 6 +- .../cop/rspec/repeated_include_example.rb | 5 +- lib/rubocop/cop/rspec/shared_context.rb | 22 +- lib/rubocop/cop/rspec/shared_examples.rb | 4 +- lib/rubocop/cop/rspec/stubbed_mock.rb | 2 +- lib/rubocop/cop/rspec/subject_stub.rb | 2 +- lib/rubocop/rspec.rb | 12 - lib/rubocop/rspec/align_let_brace.rb | 2 +- lib/rubocop/rspec/concept.rb | 4 +- lib/rubocop/rspec/example_group.rb | 19 +- lib/rubocop/rspec/hook.rb | 2 +- lib/rubocop/rspec/inject.rb | 6 +- lib/rubocop/rspec/language.rb | 252 ++++++++++-------- lib/rubocop/rspec/language/node_pattern.rb | 31 +-- spec/rubocop/cop/rspec/base_spec.rb | 60 +++++ .../cop/rspec/multiple_subjects_spec.rb | 2 - spec/rubocop/rspec/example_group_spec.rb | 8 +- spec/rubocop/rspec/example_spec.rb | 8 +- spec/rubocop/rspec/hook_spec.rb | 8 +- .../rspec/language/selector_set_spec.rb | 53 ---- .../default_rspec_language_config_context.rb | 27 ++ 52 files changed, 487 insertions(+), 308 deletions(-) delete mode 100644 lib/rubocop/rspec.rb delete mode 100644 spec/rubocop/rspec/language/selector_set_spec.rb create mode 100644 spec/shared/default_rspec_language_config_context.rb diff --git a/CHANGELOG.md b/CHANGELOG.md index 364b42faf..789a90dde 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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) @@ -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 diff --git a/config/default.yml b/config/default.yml index 9139e3e4c..19e5eccc0 100644 --- a/config/default.yml +++ b/config/default.yml @@ -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. diff --git a/lib/rubocop-rspec.rb b/lib/rubocop-rspec.rb index cc08c73dd..2b9cef84d 100644 --- a/lib/rubocop-rspec.rb +++ b/lib/rubocop-rspec.rb @@ -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' diff --git a/lib/rubocop/cop/rspec/align_left_let_brace.rb b/lib/rubocop/cop/rspec/align_left_let_brace.rb index ebe3189f7..1d943c23e 100644 --- a/lib/rubocop/cop/rspec/align_left_let_brace.rb +++ b/lib/rubocop/cop/rspec/align_left_let_brace.rb @@ -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( @@ -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 diff --git a/lib/rubocop/cop/rspec/align_right_let_brace.rb b/lib/rubocop/cop/rspec/align_right_let_brace.rb index 62c88bc04..93e7fc464 100644 --- a/lib/rubocop/cop/rspec/align_right_let_brace.rb +++ b/lib/rubocop/cop/rspec/align_right_let_brace.rb @@ -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( @@ -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 diff --git a/lib/rubocop/cop/rspec/base.rb b/lib/rubocop/cop/rspec/base.rb index e9e47b0a7..93c174a7e 100644 --- a/lib/rubocop/cop/rspec/base.rb +++ b/lib/rubocop/cop/rspec/base.rb @@ -6,7 +6,7 @@ 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 @@ -14,6 +14,13 @@ class Base < ::RuboCop::Cop::Base 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 diff --git a/lib/rubocop/cop/rspec/be.rb b/lib/rubocop/cop/rspec/be.rb index 5f388c299..af2cfca89 100644 --- a/lib/rubocop/cop/rspec/be.rb +++ b/lib/rubocop/cop/rspec/be.rb @@ -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) diff --git a/lib/rubocop/cop/rspec/capybara/current_path_expectation.rb b/lib/rubocop/cop/rspec/capybara/current_path_expectation.rb index 9f4f1fd51..5d692843b 100644 --- a/lib/rubocop/cop/rspec/capybara/current_path_expectation.rb +++ b/lib/rubocop/cop/rspec/capybara/current_path_expectation.rb @@ -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 diff --git a/lib/rubocop/cop/rspec/capybara/feature_methods.rb b/lib/rubocop/cop/rspec/capybara/feature_methods.rb index 5ff784806..96cdbd7ad 100644 --- a/lib/rubocop/cop/rspec/capybara/feature_methods.rb +++ b/lib/rubocop/cop/rspec/capybara/feature_methods.rb @@ -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 diff --git a/lib/rubocop/cop/rspec/described_class.rb b/lib/rubocop/cop/rspec/described_class.rb index 6f4e945d0..0d5deb2c4 100644 --- a/lib/rubocop/cop/rspec/described_class.rb +++ b/lib/rubocop/cop/rspec/described_class.rb @@ -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}' diff --git a/lib/rubocop/cop/rspec/described_class_module_wrapping.rb b/lib/rubocop/cop/rspec/described_class_module_wrapping.rb index b5b9c867e..044ec85e2 100644 --- a/lib/rubocop/cop/rspec/described_class_module_wrapping.rb +++ b/lib/rubocop/cop/rspec/described_class_module_wrapping.rb @@ -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 diff --git a/lib/rubocop/cop/rspec/dialect.rb b/lib/rubocop/cop/rspec/dialect.rb index d952d6958..30cd5b559 100644 --- a/lib/rubocop/cop/rspec/dialect.rb +++ b/lib/rubocop/cop/rspec/dialect.rb @@ -47,7 +47,7 @@ class Dialect < Base MSG = 'Prefer `%s` over `%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) diff --git a/lib/rubocop/cop/rspec/empty_example_group.rb b/lib/rubocop/cop/rspec/empty_example_group.rb index e2aca88b3..669ecf65b 100644 --- a/lib/rubocop/cop/rspec/empty_example_group.rb +++ b/lib/rubocop/cop/rspec/empty_example_group.rb @@ -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) @@ -95,11 +95,10 @@ class EmptyExampleGroup < Base # @return [Array] 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 @@ -120,7 +119,7 @@ class EmptyExampleGroup < Base # @param node [RuboCop::AST::Node] # @return [Array] 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) diff --git a/lib/rubocop/cop/rspec/empty_hook.rb b/lib/rubocop/cop/rspec/empty_hook.rb index e272a7bb2..09cb77e45 100644 --- a/lib/rubocop/cop/rspec/empty_hook.rb +++ b/lib/rubocop/cop/rspec/empty_hook.rb @@ -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) diff --git a/lib/rubocop/cop/rspec/empty_line_after_subject.rb b/lib/rubocop/cop/rspec/empty_line_after_subject.rb index dd8beaaa1..4f7735050 100644 --- a/lib/rubocop/cop/rspec/empty_line_after_subject.rb +++ b/lib/rubocop/cop/rspec/empty_line_after_subject.rb @@ -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 diff --git a/lib/rubocop/cop/rspec/expect_actual.rb b/lib/rubocop/cop/rspec/expect_actual.rb index 2b897ad8e..b520882d8 100644 --- a/lib/rubocop/cop/rspec/expect_actual.rb +++ b/lib/rubocop/cop/rspec/expect_actual.rb @@ -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? $_ $_ ...) diff --git a/lib/rubocop/cop/rspec/expect_in_hook.rb b/lib/rubocop/cop/rspec/expect_in_hook.rb index 79f7c929c..dc9d6fcc4 100644 --- a/lib/rubocop/cop/rspec/expect_in_hook.rb +++ b/lib/rubocop/cop/rspec/expect_in_hook.rb @@ -23,7 +23,7 @@ module RSpec class ExpectInHook < Base MSG = 'Do not use `%s` in `%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) diff --git a/lib/rubocop/cop/rspec/focus.rb b/lib/rubocop/cop/rspec/focus.rb index d49c77f9e..4e106efeb 100644 --- a/lib/rubocop/cop/rspec/focus.rb +++ b/lib/rubocop/cop/rspec/focus.rb @@ -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| diff --git a/lib/rubocop/cop/rspec/hook_argument.rb b/lib/rubocop/cop/rspec/hook_argument.rb index b722c10ab..fc8a1e767 100644 --- a/lib/rubocop/cop/rspec/hook_argument.rb +++ b/lib/rubocop/cop/rspec/hook_argument.rb @@ -64,13 +64,11 @@ class HookArgument < Base IMPLICIT_MSG = 'Omit the default `%p` argument for RSpec hooks.' EXPLICIT_MSG = 'Use `%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| diff --git a/lib/rubocop/cop/rspec/hooks_before_examples.rb b/lib/rubocop/cop/rspec/hooks_before_examples.rb index fa9eea0b9..2c8bc27af 100644 --- a/lib/rubocop/cop/rspec/hooks_before_examples.rb +++ b/lib/rubocop/cop/rspec/hooks_before_examples.rb @@ -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 diff --git a/lib/rubocop/cop/rspec/implicit_expect.rb b/lib/rubocop/cop/rspec/implicit_expect.rb index c08fd877d..aeb9b974f 100644 --- a/lib/rubocop/cop/rspec/implicit_expect.rb +++ b/lib/rubocop/cop/rspec/implicit_expect.rb @@ -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 diff --git a/lib/rubocop/cop/rspec/let_before_examples.rb b/lib/rubocop/cop/rspec/let_before_examples.rb index aeacb76fb..e01354b95 100644 --- a/lib/rubocop/cop/rspec/let_before_examples.rb +++ b/lib/rubocop/cop/rspec/let_before_examples.rb @@ -37,8 +37,8 @@ class LetBeforeExamples < 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 diff --git a/lib/rubocop/cop/rspec/let_setup.rb b/lib/rubocop/cop/rspec/let_setup.rb index 819ae5e4c..b4c929023 100644 --- a/lib/rubocop/cop/rspec/let_setup.rb +++ b/lib/rubocop/cop/rspec/let_setup.rb @@ -29,10 +29,13 @@ class LetSetup < Base MSG = 'Do not use `let!` to setup objects not referenced in tests.' def_node_matcher :example_or_shared_group_or_including?, - ( - ExampleGroups::ALL + SharedGroups::ALL + - Includes::ALL - ).block_pattern + block_pattern(<<~PATTERN) + { + #SharedGroups.all + #ExampleGroups.all + #Includes.all + } + PATTERN def_node_matcher :let_bang, <<-PATTERN { diff --git a/lib/rubocop/cop/rspec/message_spies.rb b/lib/rubocop/cop/rspec/message_spies.rb index e1460d2c0..fa30d09ee 100644 --- a/lib/rubocop/cop/rspec/message_spies.rb +++ b/lib/rubocop/cop/rspec/message_spies.rb @@ -36,7 +36,7 @@ class MessageSpies < Base SUPPORTED_STYLES = %w[have_received receive].freeze def_node_matcher :message_expectation, %( - (send (send nil? :expect $_) #{Runners::ALL.node_pattern_union} ...) + (send (send nil? :expect $_) #Runners.all ...) ) def_node_search :receive_message, %( diff --git a/lib/rubocop/cop/rspec/mixin/top_level_group.rb b/lib/rubocop/cop/rspec/mixin/top_level_group.rb index 7407b0bb7..cbd12a1fd 100644 --- a/lib/rubocop/cop/rspec/mixin/top_level_group.rb +++ b/lib/rubocop/cop/rspec/mixin/top_level_group.rb @@ -6,14 +6,9 @@ module RSpec # Helper methods for top level example group cops module TopLevelGroup extend RuboCop::NodePattern::Macros - include RuboCop::RSpec::Language - - def_node_matcher :example_or_shared_group?, - (ExampleGroups::ALL + SharedGroups::ALL).block_pattern def on_new_investigation super - return unless root_node top_level_groups.each do |node| @@ -24,9 +19,7 @@ def on_new_investigation def top_level_groups @top_level_groups ||= - top_level_nodes(root_node).select do |node| - example_or_shared_group?(node) - end + top_level_nodes(root_node).select { |n| spec_group?(n) } end private diff --git a/lib/rubocop/cop/rspec/mixin/variable.rb b/lib/rubocop/cop/rspec/mixin/variable.rb index c06161690..5a855c2ab 100644 --- a/lib/rubocop/cop/rspec/mixin/variable.rb +++ b/lib/rubocop/cop/rspec/mixin/variable.rb @@ -5,11 +5,13 @@ module Cop module RSpec # Helps check offenses with variable definitions module Variable - include RuboCop::RSpec::Language extend RuboCop::NodePattern::Macros + Subjects = RuboCop::RSpec::Language::Subjects + Helpers = RuboCop::RSpec::Language::Helpers + def_node_matcher :variable_definition?, <<~PATTERN - (send nil? #{(Helpers::ALL + Subject::ALL).node_pattern_union} + (send nil? {#Subjects.all #Helpers.all} $({sym str dsym dstr} ...) ...) PATTERN end diff --git a/lib/rubocop/cop/rspec/multiple_expectations.rb b/lib/rubocop/cop/rspec/multiple_expectations.rb index 865e2a696..79abeb1c7 100644 --- a/lib/rubocop/cop/rspec/multiple_expectations.rb +++ b/lib/rubocop/cop/rspec/multiple_expectations.rb @@ -60,7 +60,7 @@ class MultipleExpectations < Base } ...) PATTERN - def_node_matcher :expect?, Expectations::ALL.send_pattern + def_node_matcher :expect?, send_pattern('#Expectations.all') def_node_matcher :aggregate_failures_block?, <<-PATTERN (block (send nil? :aggregate_failures ...) ...) PATTERN diff --git a/lib/rubocop/cop/rspec/multiple_memoized_helpers.rb b/lib/rubocop/cop/rspec/multiple_memoized_helpers.rb index 2489a8632..674b383b9 100644 --- a/lib/rubocop/cop/rspec/multiple_memoized_helpers.rb +++ b/lib/rubocop/cop/rspec/multiple_memoized_helpers.rb @@ -101,6 +101,7 @@ def on_block(node) end def on_new_investigation + super @example_group_memoized_helpers = {} end @@ -128,6 +129,7 @@ def helpers(node) def variable_nodes(node) example_group = RuboCop::RSpec::ExampleGroup.new(node) + if allow_subject? example_group.lets else diff --git a/lib/rubocop/cop/rspec/named_subject.rb b/lib/rubocop/cop/rspec/named_subject.rb index 44d30a7b1..b462d5fae 100644 --- a/lib/rubocop/cop/rspec/named_subject.rb +++ b/lib/rubocop/cop/rspec/named_subject.rb @@ -42,24 +42,20 @@ module RSpec # it { is_expected.to be_valid } # end class NamedSubject < Base - MSG = 'Name your test subject if you need '\ - 'to reference it explicitly.' + MSG = 'Name your test subject if you need to reference it explicitly.' - def_node_matcher :rspec_block?, <<-PATTERN - { - #{Examples::ALL.block_pattern} - #{Hooks::ALL.block_pattern} - } - PATTERN + def_node_matcher :example_or_hook_block?, + block_pattern('{#Examples.all #Hooks.all}') - def_node_matcher :shared_example?, <<-PATTERN - #{SharedGroups::EXAMPLES.block_pattern} - PATTERN + def_node_matcher :shared_example?, + block_pattern('#SharedGroups.examples') def_node_search :subject_usage, '$(send nil? :subject)' def on_block(node) - return if !rspec_block?(node) || ignored_shared_example?(node) + if !example_or_hook_block?(node) || ignored_shared_example?(node) + return + end subject_usage(node) do |subject_node| add_offense(subject_node.loc.selector) diff --git a/lib/rubocop/cop/rspec/overwriting_setup.rb b/lib/rubocop/cop/rspec/overwriting_setup.rb index 5e0cbe14b..973e9e007 100644 --- a/lib/rubocop/cop/rspec/overwriting_setup.rb +++ b/lib/rubocop/cop/rspec/overwriting_setup.rb @@ -24,7 +24,8 @@ module RSpec class OverwritingSetup < Base MSG = '`%s` is already defined.' - def_node_matcher :setup?, (Helpers::ALL + Subject::ALL).block_pattern + def_node_matcher :setup?, block_pattern('{#Helpers.all #Subjects.all}') + def_node_matcher :first_argument_name, '(send _ _ ({str sym} $_))' def on_block(node) diff --git a/lib/rubocop/cop/rspec/pending.rb b/lib/rubocop/cop/rspec/pending.rb index 830c18a7a..4b04ab7ff 100644 --- a/lib/rubocop/cop/rspec/pending.rb +++ b/lib/rubocop/cop/rspec/pending.rb @@ -34,10 +34,10 @@ module RSpec class Pending < Base MSG = 'Pending spec found.' - PENDING = Examples::PENDING + Examples::SKIPPED + ExampleGroups::SKIPPED - SKIPPABLE = ExampleGroups::GROUPS + Examples::EXAMPLES - - def_node_matcher :skippable?, SKIPPABLE.send_pattern + def_node_matcher :skippable?, + send_pattern(<<~PATTERN) + {#ExampleGroups.regular #Examples.regular} + PATTERN def_node_matcher :skipped_in_metadata?, <<-PATTERN { @@ -47,7 +47,15 @@ class Pending < Base PATTERN def_node_matcher :skip_or_pending?, '{(sym :skip) (sym :pending)}' - def_node_matcher :pending_block?, PENDING.send_pattern + + def_node_matcher :pending_block?, + send_pattern(<<~PATTERN) + { + #ExampleGroups.skipped + #Examples.skipped + #Examples.pending + } + PATTERN def on_send(node) return unless pending_block?(node) || skipped?(node) diff --git a/lib/rubocop/cop/rspec/predicate_matcher.rb b/lib/rubocop/cop/rspec/predicate_matcher.rb index 13d1f6fbe..77c7e1d42 100644 --- a/lib/rubocop/cop/rspec/predicate_matcher.rb +++ b/lib/rubocop/cop/rspec/predicate_matcher.rb @@ -30,7 +30,7 @@ def check_inflected(node) (send nil? :expect { (block $(send !nil? #predicate? ...) ...) $(send !nil? #predicate? ...)}) - $#{Runners::ALL.node_pattern_union} + $#Runners.all $#boolean_matcher?) PATTERN @@ -155,7 +155,7 @@ def check_explicit(node) # rubocop:disable Metrics/MethodLength def_node_matcher :predicate_matcher?, <<-PATTERN (send (send nil? :expect $!nil?) - #{Runners::ALL.node_pattern_union} + #Runners.all {$(send nil? #predicate_matcher_name? ...) (block $(send nil? #predicate_matcher_name? ...) ...)}) PATTERN @@ -164,7 +164,7 @@ def check_explicit(node) # rubocop:disable Metrics/MethodLength (block (send (send nil? :expect $!nil?) - #{Runners::ALL.node_pattern_union} + #Runners.all $(send nil? #predicate_matcher_name?)) ...) PATTERN diff --git a/lib/rubocop/cop/rspec/repeated_include_example.rb b/lib/rubocop/cop/rspec/repeated_include_example.rb index c631aa06c..57d02a2a4 100644 --- a/lib/rubocop/cop/rspec/repeated_include_example.rb +++ b/lib/rubocop/cop/rspec/repeated_include_example.rb @@ -54,10 +54,11 @@ class RepeatedIncludeExample < Base (begin <#include_examples? #include_examples? ...>) PATTERN - def_node_matcher :include_examples?, Includes::EXAMPLES.send_pattern + def_node_matcher :include_examples?, + send_pattern('#Includes.examples') def_node_matcher :shared_examples_name, <<-PATTERN - (send _ #{Includes::EXAMPLES.node_pattern_union} $_ ...) + (send _ #Includes.examples $_ ...) PATTERN def on_begin(node) diff --git a/lib/rubocop/cop/rspec/shared_context.rb b/lib/rubocop/cop/rspec/shared_context.rb index 34a908888..c15f8a276 100644 --- a/lib/rubocop/cop/rspec/shared_context.rb +++ b/lib/rubocop/cop/rspec/shared_context.rb @@ -59,14 +59,24 @@ class SharedContext < Base MSG_CONTEXT = "Use `shared_context` when you don't "\ 'define examples.' - examples = (Examples::ALL + Includes::EXAMPLES) - def_node_search :examples?, examples.send_pattern + def_node_search :examples?, + send_pattern('{#Includes.examples #Examples.all}') - context = (Hooks::ALL + Helpers::ALL + Includes::CONTEXT + Subject::ALL) - def_node_search :context?, context.send_pattern + def_node_search :context?, <<-PATTERN + ( + send #rspec? { + #Subjects.all + #Helpers.all + #Includes.context + #Hooks.all + } ... + ) + PATTERN - def_node_matcher :shared_context, SharedGroups::CONTEXT.block_pattern - def_node_matcher :shared_example, SharedGroups::EXAMPLES.block_pattern + def_node_matcher :shared_context, + block_pattern('#SharedGroups.context') + def_node_matcher :shared_example, + block_pattern('#SharedGroups.examples') def on_block(node) context_with_only_examples(node) do diff --git a/lib/rubocop/cop/rspec/shared_examples.rb b/lib/rubocop/cop/rspec/shared_examples.rb index 10732d30c..7bdf2d408 100644 --- a/lib/rubocop/cop/rspec/shared_examples.rb +++ b/lib/rubocop/cop/rspec/shared_examples.rb @@ -24,7 +24,9 @@ class SharedExamples < Base extend AutoCorrector def_node_matcher :shared_examples, - (SharedGroups::ALL + Includes::ALL).send_pattern + send_pattern( + '{#SharedGroups.all #Includes.all}' + ) def on_send(node) shared_examples(node) do diff --git a/lib/rubocop/cop/rspec/stubbed_mock.rb b/lib/rubocop/cop/rspec/stubbed_mock.rb index bd508ceaf..cdf4b9be7 100644 --- a/lib/rubocop/cop/rspec/stubbed_mock.rb +++ b/lib/rubocop/cop/rspec/stubbed_mock.rb @@ -60,7 +60,7 @@ class StubbedMock < Base # @yield [RuboCop::AST::Node] expectation, method name, matcher def_node_matcher :expectation, <<~PATTERN (send - $(send nil? $#{Expectations::ALL.node_pattern_union} ...) + $(send nil? $#Expectations.all ...) :to $_) PATTERN diff --git a/lib/rubocop/cop/rspec/subject_stub.rb b/lib/rubocop/cop/rspec/subject_stub.rb index d2d247594..ca5d04d6a 100644 --- a/lib/rubocop/cop/rspec/subject_stub.rb +++ b/lib/rubocop/cop/rspec/subject_stub.rb @@ -66,7 +66,7 @@ class SubjectStub < Base (send nil? { :expect :allow } (send nil? {% :subject})) (send nil? :is_expected) } - #{Runners::ALL.node_pattern_union} + #Runners.all #message_expectation_matcher? ) PATTERN diff --git a/lib/rubocop/rspec.rb b/lib/rubocop/rspec.rb deleted file mode 100644 index 7c78f4e43..000000000 --- a/lib/rubocop/rspec.rb +++ /dev/null @@ -1,12 +0,0 @@ -# frozen_string_literal: true - -module RuboCop - # RuboCop RSpec project namespace - module RSpec - PROJECT_ROOT = Pathname.new(__dir__).parent.parent.expand_path.freeze - CONFIG_DEFAULT = PROJECT_ROOT.join('config', 'default.yml').freeze - CONFIG = YAML.safe_load(CONFIG_DEFAULT.read).freeze - - private_constant(:CONFIG_DEFAULT, :PROJECT_ROOT) - end -end diff --git a/lib/rubocop/rspec/align_let_brace.rb b/lib/rubocop/rspec/align_let_brace.rb index 83c684bfe..23c8ac3d8 100644 --- a/lib/rubocop/rspec/align_let_brace.rb +++ b/lib/rubocop/rspec/align_let_brace.rb @@ -4,7 +4,7 @@ module RuboCop module RSpec # Shared behavior for aligning braces for single line lets class AlignLetBrace - include RuboCop::RSpec::Language::NodePattern + include RuboCop::RSpec::Language def initialize(root, token) @root = root diff --git a/lib/rubocop/rspec/concept.rb b/lib/rubocop/rspec/concept.rb index 9a66ede9d..53790a1ec 100644 --- a/lib/rubocop/rspec/concept.rb +++ b/lib/rubocop/rspec/concept.rb @@ -4,9 +4,9 @@ module RuboCop module RSpec # Wrapper for RSpec DSL methods class Concept + extend RuboCop::NodePattern::Macros + extend Language::NodePattern include Language - include Language::NodePattern - extend NodePattern::Macros def initialize(node) @node = node diff --git a/lib/rubocop/rspec/example_group.rb b/lib/rubocop/rspec/example_group.rb index d9437d8f7..9c993f9ef 100644 --- a/lib/rubocop/rspec/example_group.rb +++ b/lib/rubocop/rspec/example_group.rb @@ -10,9 +10,14 @@ class ExampleGroup < Concept # # Selectors which indicate that we should stop searching # - def_node_matcher :scope_change?, ( - ExampleGroups::ALL + SharedGroups::ALL + Includes::ALL - ).block_pattern + def_node_matcher :scope_change?, + block_pattern(<<~PATTERN) + { + #SharedGroups.all + #ExampleGroups.all + #Includes.all + } + PATTERN def lets find_all_in_scope(node, :let?) @@ -23,11 +28,15 @@ def subjects end def examples - find_all_in_scope(node, :example?).map(&Example.public_method(:new)) + find_all_in_scope(node, :example?).map do |node| + Example.new(node) + end end def hooks - find_all_in_scope(node, :hook?).map(&Hook.public_method(:new)) + find_all_in_scope(node, :hook?).map do |node| + Hook.new(node) + end end private diff --git a/lib/rubocop/rspec/hook.rb b/lib/rubocop/rspec/hook.rb index 078bf5a7a..506473ac2 100644 --- a/lib/rubocop/rspec/hook.rb +++ b/lib/rubocop/rspec/hook.rb @@ -44,7 +44,7 @@ def metadata private def valid_scope?(node) - node&.sym_type? && Hooks::Scopes::ALL.include?(node.value) + node&.sym_type? && Language::HookScopes.all(node.value) end def transform_metadata(meta) diff --git a/lib/rubocop/rspec/inject.rb b/lib/rubocop/rspec/inject.rb index 659ca8f11..02424bc2c 100644 --- a/lib/rubocop/rspec/inject.rb +++ b/lib/rubocop/rspec/inject.rb @@ -6,9 +6,11 @@ module RSpec # bit of our configuration. module Inject def self.defaults! - path = CONFIG_DEFAULT.to_s + project_root = Pathname.new(__dir__).parent.parent.parent.expand_path + config_default = project_root.join('config', 'default.yml') + path = config_default.to_s hash = ConfigLoader.send(:load_yaml_configuration, path) - config = Config.new(hash, path) + config = RuboCop::Config.new(hash, path) puts "configuration from #{path}" if ConfigLoader.debug? config = ConfigLoader.merge_with_default(config, path) ConfigLoader.instance_variable_set(:@default_configuration, config) diff --git a/lib/rubocop/rspec/language.rb b/lib/rubocop/rspec/language.rb index 63f673009..8587011fc 100644 --- a/lib/rubocop/rspec/language.rb +++ b/lib/rubocop/rspec/language.rb @@ -2,152 +2,186 @@ module RuboCop module RSpec - # RSpec public API methods that are commonly used in cops + # Contains node matchers for common RSpec DSL. + # + # RSpec allows for configuring aliases for commonly used DSL elements, e.g. + # example groups and hooks. It is possible to configure RuboCop RSpec to + # be able to properly detect these elements in the `RSpec/Language` section + # of the RuboCop YAML configuration file. + # + # In addition to providing useful matchers, this class is responsible for + # using the configured aliases. module Language - # Set of method selectors - class SelectorSet - def initialize(selectors) - @selectors = selectors.freeze - freeze - end + extend RuboCop::NodePattern::Macros + extend NodePattern - def ==(other) - selectors.eql?(other.selectors) - end + class << self + attr_accessor :config + end - def +(other) - self.class.new(selectors + other.selectors) - end + def_node_matcher :rspec?, '{(const {nil? cbase} :RSpec) nil?}' - def include?(selector) - selectors.include?(selector) - end + def_node_matcher :example_group?, block_pattern('#ExampleGroups.all') - def block_pattern - "(block #{send_pattern} ...)" - end + def_node_matcher :shared_group?, block_pattern('#SharedGroups.all') - def block_pass_pattern - "(send #rspec? #{node_pattern_union} _ block_pass)" - end + def_node_matcher :spec_group?, + block_pattern('{#SharedGroups.all #ExampleGroups.all}') - def block_or_block_pass_pattern - "{#{block_pattern} #{block_pass_pattern}}" - end + def_node_matcher :example_group_with_body?, <<-PATTERN + (block #{send_pattern('#ExampleGroups.all')} args !nil?) + PATTERN - def send_pattern - "(send #rspec? #{node_pattern_union} ...)" - end + def_node_matcher :example?, block_pattern('#Examples.all') - def send_or_block_or_block_pass_pattern - "{#{send_pattern} #{block_pattern} #{block_pass_pattern}}" - end + def_node_matcher :hook?, block_pattern('#Hooks.all') - def node_pattern_union - "{#{node_pattern}}" - end + def_node_matcher :let?, <<-PATTERN + { + #{block_pattern('#Helpers.all')} + (send #rspec? #Helpers.all _ block_pass) + } + PATTERN - def node_pattern - selectors.map(&:inspect).join(' ') - end + def_node_matcher :include?, <<-PATTERN + { + #{send_pattern('#Includes.all')} + #{block_pattern('#Includes.all')} + } + PATTERN - def to_a - selectors - end + def_node_matcher :subject?, block_pattern('#Subjects.all') - protected + module ExampleGroups # :nodoc: + class << self + def all(element) + regular(element) || + skipped(element) || + focused(element) + end - attr_reader :selectors - end + def regular(element) + Language.config['ExampleGroups']['Regular'].include?(element.to_s) + end - module ExampleGroups - GROUPS = SelectorSet.new(%i[describe context feature example_group]) - SKIPPED = SelectorSet.new(%i[xdescribe xcontext xfeature]) - FOCUSED = SelectorSet.new(%i[fdescribe fcontext ffeature]) + def focused(element) + Language.config['ExampleGroups']['Focused'].include?(element.to_s) + end - ALL = GROUPS + SKIPPED + FOCUSED + def skipped(element) + Language.config['ExampleGroups']['Skipped'].include?(element.to_s) + end + end end - module SharedGroups - EXAMPLES = SelectorSet.new(%i[shared_examples shared_examples_for]) - CONTEXT = SelectorSet.new(%i[shared_context]) + module Examples # :nodoc: + class << self + def all(element) + regular(element) || + focused(element) || + skipped(element) || + pending(element) + end + + def regular(element) + Language.config['Examples']['Regular'].include?(element.to_s) + end + + def focused(element) + Language.config['Examples']['Focused'].include?(element.to_s) + end + + def skipped(element) + Language.config['Examples']['Skipped'].include?(element.to_s) + end + + def pending(element) + Language.config['Examples']['Pending'].include?(element.to_s) + end + end + end - ALL = EXAMPLES + CONTEXT + module Expectations # :nodoc: + def self.all(element) + Language.config['Expectations'].include?(element.to_s) + end end - module Includes - EXAMPLES = SelectorSet.new( - %i[ - it_behaves_like - it_should_behave_like - include_examples - ] - ) - CONTEXT = SelectorSet.new(%i[include_context]) - - ALL = EXAMPLES + CONTEXT + module Helpers # :nodoc: + def self.all(element) + Language.config['Helpers'].include?(element.to_s) + end end - module Examples - EXAMPLES = SelectorSet.new(%i[it specify example scenario its]) - FOCUSED = SelectorSet.new(%i[fit fspecify fexample fscenario focus]) - SKIPPED = SelectorSet.new(%i[xit xspecify xexample xscenario skip]) - PENDING = SelectorSet.new(%i[pending]) + module Hooks # :nodoc: + def self.all(element) + Language.config['Hooks'].include?(element.to_s) + end + end - ALL = EXAMPLES + FOCUSED + SKIPPED + PENDING + module HookScopes # :nodoc: + def self.all(element) + Language.config['HookScopes'].include?(element.to_s) + end end - module Hooks - ALL = SelectorSet.new( - %i[ - prepend_before - before - append_before - around - prepend_after - after - append_after - ] - ) - - module Scopes - ALL = SelectorSet.new( - %i[ - each - example - context - all - suite - ] - ) + module Includes # :nodoc: + class << self + def all(element) + examples(element) || + context(element) + end + + def examples(element) + Language.config['Includes']['Examples'].include?(element.to_s) + end + + def context(element) + Language.config['Includes']['Context'].include?(element.to_s) + end end end - module Helpers - ALL = SelectorSet.new(%i[let let!]) + module Runners # :nodoc: + def self.all(element) + Language.config['Runners'].include?(element.to_s) + end end - module Subject - ALL = SelectorSet.new(%i[subject subject!]) + module SharedGroups # :nodoc: + class << self + def all(element) + examples(element) || + context(element) + end + + def examples(element) + Language.config['SharedGroups']['Examples'].include?(element.to_s) + end + + def context(element) + Language.config['SharedGroups']['Context'].include?(element.to_s) + end + end end - module Expectations - ALL = SelectorSet.new(%i[expect is_expected expect_any_instance_of]) + module Subjects # :nodoc: + def self.all(element) + Language.config['Subjects'].include?(element.to_s) + end end - module Runners - ALL = SelectorSet.new(%i[to to_not not_to]) + # This is used in Dialect and DescribeClass cops to detect RSpec blocks. + module ALL # :nodoc: + def self.all(element) + [ExampleGroups, Examples, Expectations, Helpers, Hooks, Includes, + Runners, SharedGroups, Subjects] + .find { |concept| concept.all(element) } + end end - ALL = - ExampleGroups::ALL + - SharedGroups::ALL + - Examples::ALL + - Hooks::ALL + - Helpers::ALL + - Subject::ALL + - Expectations::ALL + - Runners::ALL + private_constant :ExampleGroups, :Examples, :Expectations, :Hooks, + :Includes, :Runners, :SharedGroups, :ALL end end end diff --git a/lib/rubocop/rspec/language/node_pattern.rb b/lib/rubocop/rspec/language/node_pattern.rb index 82b0bbb05..486516206 100644 --- a/lib/rubocop/rspec/language/node_pattern.rb +++ b/lib/rubocop/rspec/language/node_pattern.rb @@ -3,32 +3,15 @@ module RuboCop module RSpec module Language - # Common node matchers used for matching against the rspec DSL + # Helper methods to detect RSpec DSL used with send and block module NodePattern - extend RuboCop::NodePattern::Macros + def send_pattern(string) + "(send #rspec? #{string} ...)" + end - def_node_matcher :rspec?, '{(const {nil? cbase} :RSpec) nil?}' - - def_node_matcher :example_group?, ExampleGroups::ALL.block_pattern - def_node_matcher :shared_group?, SharedGroups::ALL.block_pattern - - spec_groups = ExampleGroups::ALL + SharedGroups::ALL - def_node_matcher :spec_group?, spec_groups.block_pattern - - def_node_matcher :example_group_with_body?, <<-PATTERN - (block #{ExampleGroups::ALL.send_pattern} args !nil?) - PATTERN - - def_node_matcher :example?, Examples::ALL.block_pattern - - def_node_matcher :hook?, Hooks::ALL.block_pattern - - def_node_matcher :let?, Helpers::ALL.block_or_block_pass_pattern - - def_node_matcher :include?, - Includes::ALL.send_or_block_or_block_pass_pattern - - def_node_matcher :subject?, Subject::ALL.block_pattern + def block_pattern(string) + "(block #{send_pattern(string)} ...)" + end end end end diff --git a/spec/rubocop/cop/rspec/base_spec.rb b/spec/rubocop/cop/rspec/base_spec.rb index 18fcdfcf2..97b38b1dc 100644 --- a/spec/rubocop/cop/rspec/base_spec.rb +++ b/spec/rubocop/cop/rspec/base_spec.rb @@ -76,4 +76,64 @@ def on_send(node) RUBY end end + + describe 'DSL alias configuration' do + before do + stub_const('RuboCop::RSpec::ExampleGroupHaterCop', + Class.new(described_class) do + def on_block(node) + example_group?(node) do + add_offense(node, message: 'I flag example groups') + end + end + end) + end + + let(:cop_class) { RuboCop::RSpec::ExampleGroupHaterCop } + + shared_examples_for 'it detects `describe`' do + it 'detects `describe` as an example group' do + expect_offense(<<~RUBY) + describe 'ouch oh' do + ^^^^^^^^^^^^^^^^^^^^^ I flag example groups + it { } + end + RUBY + end + end + + context 'with the default config' do + it 'does not detect `epic` as an example group' do + expect_no_offenses(<<~RUBY) + epic 'great achievements or events is narrated in elevated style' do + ballad 'slays Minotaur' do + # ... + end + end + RUBY + end + + include_examples 'it detects `describe`' + end + + context 'when `epic` is set as an alias to example group' do + before do + other_cops['RSpec']['Language']['ExampleGroups']['Regular'] + .push('epic') + end + + it 'detects `epic` as an example group' do + expect_offense(<<~RUBY) + epic 'great achievements or events is narrated in elevated style' do + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ I flag example groups + ballad 'slays Minotaur' do + # ... + end + end + RUBY + end + + include_examples 'it detects `describe`' + end + end end diff --git a/spec/rubocop/cop/rspec/multiple_subjects_spec.rb b/spec/rubocop/cop/rspec/multiple_subjects_spec.rb index 9b87ce78b..f62ce54e2 100644 --- a/spec/rubocop/cop/rspec/multiple_subjects_spec.rb +++ b/spec/rubocop/cop/rspec/multiple_subjects_spec.rb @@ -1,8 +1,6 @@ # frozen_string_literal: true RSpec.describe RuboCop::Cop::RSpec::MultipleSubjects do - let(:cop) { described_class.new } - it 'registers an offense for every overwritten subject' do expect_offense(<<-RUBY) describe 'hello there' do diff --git a/spec/rubocop/rspec/example_group_spec.rb b/spec/rubocop/rspec/example_group_spec.rb index e5fba6d70..78869cb1f 100644 --- a/spec/rubocop/rspec/example_group_spec.rb +++ b/spec/rubocop/rspec/example_group_spec.rb @@ -1,10 +1,12 @@ # frozen_string_literal: true -RSpec.describe RuboCop::RSpec::ExampleGroup do +RSpec.describe RuboCop::RSpec::ExampleGroup, :config do include RuboCop::AST::Sexp subject(:group) { described_class.new(parse_source(source).ast) } + let(:cop_class) { RuboCop::Cop::RSpec::Base } + let(:source) do <<-RUBY RSpec.describe Foo do @@ -38,6 +40,10 @@ ].map { |node| RuboCop::RSpec::Example.new(node) } end + # Trigger setting of the `Language` in the case when this spec + # runs before cops' specs that set it. + before { cop.on_new_investigation } + it 'exposes examples in scope' do expect(group.examples).to eql(example_nodes) end diff --git a/spec/rubocop/rspec/example_spec.rb b/spec/rubocop/rspec/example_spec.rb index 9cbfae4b1..929972642 100644 --- a/spec/rubocop/rspec/example_spec.rb +++ b/spec/rubocop/rspec/example_spec.rb @@ -1,8 +1,14 @@ # frozen_string_literal: true -RSpec.describe RuboCop::RSpec::Example do +RSpec.describe RuboCop::RSpec::Example, :config do include RuboCop::AST::Sexp + let(:cop_class) { RuboCop::Cop::RSpec::Base } + + # Trigger setting of the `Language` in the case when this spec + # runs before cops' specs that set it. + before { cop.on_new_investigation } + def example(source) described_class.new(parse_source(source).ast) end diff --git a/spec/rubocop/rspec/hook_spec.rb b/spec/rubocop/rspec/hook_spec.rb index e5bdc2c1e..dffb7d63d 100644 --- a/spec/rubocop/rspec/hook_spec.rb +++ b/spec/rubocop/rspec/hook_spec.rb @@ -1,8 +1,14 @@ # frozen_string_literal: true -RSpec.describe RuboCop::RSpec::Hook do +RSpec.describe RuboCop::RSpec::Hook, :config do include RuboCop::AST::Sexp + let(:cop_class) { RuboCop::Cop::RSpec::Base } + + # Trigger setting of the `Language` in the case when this spec + # runs before cops' specs that set it. + before { cop.on_new_investigation } + def hook(source) described_class.new(parse_source(source).ast) end diff --git a/spec/rubocop/rspec/language/selector_set_spec.rb b/spec/rubocop/rspec/language/selector_set_spec.rb deleted file mode 100644 index 6298c4fc7..000000000 --- a/spec/rubocop/rspec/language/selector_set_spec.rb +++ /dev/null @@ -1,53 +0,0 @@ -# frozen_string_literal: true - -RSpec.describe RuboCop::RSpec::Language::SelectorSet do - subject(:selector_set) { described_class.new(%i[foo bar]) } - - it 'composes sets' do - combined = selector_set + described_class.new(%i[baz]) - - expect(combined).to eq(described_class.new(%i[foo bar baz])) - end - - it 'compares by value' do - expect(selector_set).not_to eq(described_class.new(%i[foo bar baz])) - end - - describe '#include?' do - it 'returns false for selectors not in the set' do - expect(selector_set.include?(:baz)).to be(false) - end - - it 'returns true for selectors in the set' do - expect(selector_set.include?(:foo)).to be(true) - end - end - - describe '#node_pattern' do - it 'builds a node pattern' do - expect(selector_set.node_pattern).to eql(':foo :bar') - end - end - - describe '#node_pattern_union' do - it 'builds a node pattern union' do - expect(selector_set.node_pattern_union).to eql('{:foo :bar}') - end - end - - describe '#send_pattern' do - it 'builds a send matching pattern' do - expect(selector_set.send_pattern).to eql( - '(send #rspec? {:foo :bar} ...)' - ) - end - end - - describe '#block_pattern' do - it 'builds a block matching pattern' do - expect(selector_set.block_pattern).to eql( - '(block (send #rspec? {:foo :bar} ...) ...)' - ) - end - end -end diff --git a/spec/shared/default_rspec_language_config_context.rb b/spec/shared/default_rspec_language_config_context.rb new file mode 100644 index 000000000..350f82715 --- /dev/null +++ b/spec/shared/default_rspec_language_config_context.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +RSpec.shared_context 'with default AllCops.RSpec.Language config', :config do + # Deep duplication is needed to prevent config leakage between examples + let(:other_cops) do + default_language = RuboCop::ConfigLoader + .default_configuration['RSpec']['Language'] + default_include = RuboCop::ConfigLoader + .default_configuration['RSpec']['Include'] + { 'RSpec' => + { + 'Include' => default_include, + 'Language' => deep_dup(default_language) + } } + end + + def deep_dup(object) + case object + when Array + object.map { |item| deep_dup(item) } + when Hash + object.transform_values(&method(:deep_dup)) + else + object # only collections undergo modifications and need duping + end + end +end