diff --git a/config/default.yml b/config/default.yml index 3df0e982e..e72514189 100644 --- a/config/default.yml +++ b/config/default.yml @@ -51,6 +51,9 @@ RSpec/InstanceVariable: Description: 'Checks for the usage of instance variables.' Enabled: true +RSpec/LetSetup: + Description: 'Checks for `let!` being used for test setup.' + RSpec/FilePath: Description: 'Checks the file and folder naming of the spec file.' Enabled: true diff --git a/lib/rubocop-rspec.rb b/lib/rubocop-rspec.rb index 117f5f35c..4dbe8b3aa 100644 --- a/lib/rubocop-rspec.rb +++ b/lib/rubocop-rspec.rb @@ -21,6 +21,7 @@ require 'rubocop/cop/rspec/file_path' require 'rubocop/cop/rspec/focus' require 'rubocop/cop/rspec/instance_variable' +require 'rubocop/cop/rspec/let_setup' require 'rubocop/cop/rspec/multiple_describes' require 'rubocop/cop/rspec/multiple_expectations' require 'rubocop/cop/rspec/named_subject' diff --git a/lib/rubocop/cop/rspec/let_setup.rb b/lib/rubocop/cop/rspec/let_setup.rb new file mode 100644 index 000000000..9379354da --- /dev/null +++ b/lib/rubocop/cop/rspec/let_setup.rb @@ -0,0 +1,37 @@ +# frozen_string_literal: true + +module RuboCop + module Cop + module RSpec + class LetSetup < Cop + include RuboCop::RSpec::TopLevelDescribe, RuboCop::RSpec::Language + + MSG = 'Do not use `let!` for test setup.'.freeze + + def_node_search :let_bang, '(block $(send nil :let! (sym $_)) args ...)' + + def_node_search :method_called?, '(send nil %)' + + def_node_matcher :example_group?, <<-PATTERN + (block (send _ {#{ExampleGroups::ALL.to_node_pattern}} ...) ...) + PATTERN + + def on_block(node) + return unless example_group?(node) + + unused_let_bang(node) do |let| + add_offense(let, :expression) + end + end + + private + + def unused_let_bang(node) + let_bang(node) do |method_send, method_name| + yield(method_send) unless method_called?(node, method_name) + end + end + end + end + end +end diff --git a/spec/rubocop/cop/rspec/let_setup_spec.rb b/spec/rubocop/cop/rspec/let_setup_spec.rb new file mode 100644 index 000000000..37bfe405e --- /dev/null +++ b/spec/rubocop/cop/rspec/let_setup_spec.rb @@ -0,0 +1,66 @@ +# frozen_string_literal: true + +describe RuboCop::Cop::RSpec::LetSetup do + subject(:cop) { described_class.new } + + it 'complains when let! is used and not referenced' do + expect_violation(<<-RUBY) + describe Foo do + let!(:foo) { bar } + ^^^^^^^^^^ Do not use `let!` for test setup. + + it 'does not use foo' do + expect(baz).to eq(qux) + end + end + RUBY + end + + it 'ignores let! when used in `before`' do + expect_no_violations(<<-RUBY) + describe Foo do + let!(:foo) { bar } + + before do + foo + end + + it 'does not use foo' do + expect(baz).to eq(qux) + end + end + RUBY + end + + it 'ignores let! when used in example' do + expect_no_violations(<<-RUBY) + describe Foo do + let!(:foo) { bar } + + it 'uses foo' do + foo + expect(baz).to eq(qux) + end + end + RUBY + end + + it 'complains when let! is used and not referenced within nested group' do + expect_violation(<<-RUBY) + describe Foo do + context 'when something special happens' do + let!(:foo) { bar } + ^^^^^^^^^^ Do not use `let!` for test setup. + + it 'does not use foo' do + expect(baz).to eq(qux) + end + end + + it 'references some other foo' do + foo + end + end + RUBY + end +end