From ab0a6455c790874cafd2deff9b7b2da8d82169f2 Mon Sep 17 00:00:00 2001 From: Sean McGivern Date: Mon, 29 Jul 2019 16:27:36 +0100 Subject: [PATCH] Add RSpec/TopLevelDescribePath cop The RSpec/FilePath cop checks that a spec file's path is correct, but only if the file describes a constant. We want to check, for any file with a top-level `describe`, whether the file path ends in `_spec.rb`. We don't care what comes before that; just that it will be executed by RSpec at all. --- rubocop/cop/rspec/top_level_describe_path.rb | 35 ++++++++++ rubocop/rubocop.rb | 1 + .../cop/rspec/top_level_describe_path_spec.rb | 67 +++++++++++++++++++ 3 files changed, 103 insertions(+) create mode 100644 rubocop/cop/rspec/top_level_describe_path.rb create mode 100644 spec/rubocop/cop/rspec/top_level_describe_path_spec.rb diff --git a/rubocop/cop/rspec/top_level_describe_path.rb b/rubocop/cop/rspec/top_level_describe_path.rb new file mode 100644 index 00000000000..61796e23af0 --- /dev/null +++ b/rubocop/cop/rspec/top_level_describe_path.rb @@ -0,0 +1,35 @@ +# frozen_string_literal: true + +require 'rubocop/rspec/top_level_describe' + +module RuboCop + module Cop + module RSpec + class TopLevelDescribePath < RuboCop::Cop::Cop + include RuboCop::RSpec::TopLevelDescribe + + MESSAGE = 'A file with a top-level `describe` must end in _spec.rb.' + SHARED_EXAMPLES = %i[shared_examples shared_examples_for].freeze + + def on_top_level_describe(node, args) + return if acceptable_file_path?(processed_source.buffer.name) + return if shared_example?(node) + + add_offense(node, message: MESSAGE) + end + + private + + def acceptable_file_path?(path) + File.fnmatch?('*_spec.rb', path) || File.fnmatch?('*/frontend/fixtures/*', path) + end + + def shared_example?(node) + node.ancestors.any? do |node| + node.respond_to?(:method_name) && SHARED_EXAMPLES.include?(node.method_name) + end + end + end + end + end +end diff --git a/rubocop/rubocop.rb b/rubocop/rubocop.rb index ba61a634d97..58a7ead6f13 100644 --- a/rubocop/rubocop.rb +++ b/rubocop/rubocop.rb @@ -32,6 +32,7 @@ require_relative 'cop/migration/update_large_table' require_relative 'cop/project_path_helper' require_relative 'cop/rspec/env_assignment' require_relative 'cop/rspec/factories_in_migration_specs' +require_relative 'cop/rspec/top_level_describe_path' require_relative 'cop/qa/element_with_pattern' require_relative 'cop/sidekiq_options_queue' require_relative 'cop/destroy_all' diff --git a/spec/rubocop/cop/rspec/top_level_describe_path_spec.rb b/spec/rubocop/cop/rspec/top_level_describe_path_spec.rb new file mode 100644 index 00000000000..258144d4000 --- /dev/null +++ b/spec/rubocop/cop/rspec/top_level_describe_path_spec.rb @@ -0,0 +1,67 @@ +# frozen_string_literal: true + +require 'fast_spec_helper' + +require 'rubocop' +require 'rubocop/rspec/support' + +require_relative '../../../../rubocop/cop/rspec/top_level_describe_path' + +describe RuboCop::Cop::RSpec::TopLevelDescribePath do + include RuboCop::RSpec::ExpectOffense + include CopHelper + + subject(:cop) { described_class.new } + + context 'when the file ends in _spec.rb' do + it 'registers no offenses' do + expect_no_offenses(<<~SOURCE.strip_indent, 'spec/foo_spec.rb') + describe 'Foo' do + end + SOURCE + end + end + + context 'when the file is a frontend fixture' do + it 'registers no offenses' do + expect_no_offenses(<<~SOURCE.strip_indent, 'spec/frontend/fixtures/foo.rb') + describe 'Foo' do + end + SOURCE + end + end + + context 'when the describe is in a shared example' do + context 'with shared_examples' do + it 'registers no offenses' do + expect_no_offenses(<<~SOURCE.strip_indent, 'spec/foo.rb') + shared_examples 'Foo' do + describe '#bar' do + end + end + SOURCE + end + end + + context 'with shared_examples_for' do + it 'registers no offenses' do + expect_no_offenses(<<~SOURCE.strip_indent, 'spec/foo.rb') + shared_examples_for 'Foo' do + describe '#bar' do + end + end + SOURCE + end + end + end + + context 'when the describe is at the top level' do + it 'marks the describe as offending' do + expect_offense(<<~SOURCE.strip_indent, 'spec/foo.rb') + describe 'Foo' do + ^^^^^^^^^^^^^^ #{described_class::MESSAGE} + end + SOURCE + end + end +end