Skip to content

Commit

Permalink
Add NoLet cop
Browse files Browse the repository at this point in the history
Fixes #862
  • Loading branch information
mockdeep committed Jan 20, 2020
1 parent e76f7d4 commit 5d70d0b
Show file tree
Hide file tree
Showing 7 changed files with 231 additions and 0 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Expand Up @@ -5,6 +5,7 @@
* Fix `RSpec/InstanceVariable` detection inside custom matchers. ([@pirj][])
* Fix `RSpec/ScatteredSetup` to distinguish hooks with different metadata. ([@pirj][])
* Add autocorrect support for `RSpec/ExpectActual` cop. ([@dduugg][], [@pirj][])
* Add `RSpec/NoLet` cop. ([@mockdeep][])

## 1.37.1 (2019-12-16)

Expand Down Expand Up @@ -477,3 +478,4 @@ Compatibility release so users can upgrade RuboCop to 0.51.0. No new features.
[@jfragoulis]: https://github.com/jfragoulis
[@ybiquitous]: https://github.com/ybiquitous
[@dduugg]: https://github.com/dduugg
[@mockdeep]: https://github.com/mockdeep
6 changes: 6 additions & 0 deletions config/default.yml
Expand Up @@ -344,6 +344,12 @@ RSpec/NestedGroups:
Max: 3
StyleGuide: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/NestedGroups

RSpec/NoLet:
Description: Checks for usage of `let` blocks in specs.
Enabled: false
AllowSubject: false
StyleGuide: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/NoLet

RSpec/NotToNot:
Description: Checks for consistent method usage for negating expectations.
EnforcedStyle: not_to
Expand Down
86 changes: 86 additions & 0 deletions lib/rubocop/cop/rspec/no_let.rb
@@ -0,0 +1,86 @@
# frozen_string_literal: true

# TODO: when finished, run `rake generate_cops_documentation` to update the docs
module RuboCop
module Cop
module RSpec
# Checks for usage of `let` blocks in specs.
#
# This cop can be configured with the option `AllowSubject` which
# will configure the cop to only register offenses on explicit calls to
# `let` and not calls to `subject`
#
# @example
# # bad
# describe MyClass do
# let(:foo) { [] }
# it { expect(foo).to be_empty }
# end
#
# describe MyClass do
# subject(:foo) { [] }
# it { expect(foo).to be_empty }
# end
#
# # good
# describe MyClass do
# it do
# foo = []
# expect(foo).to be_empty
# end
# end
#
# @example with AllowSubject configuration
#
# # rubocop.yml
# # RSpec/NoLet:
# # AllowSubject: true
#
# # bad
# describe MyClass do
# let(:foo) { [] }
# it { expect(foo).to be_empty }
# end
#
# # good
# describe MyClass do
# subject(:foo) { [] }
# it { expect(foo).to be_empty }
# end
#
# describe MyClass do
# it do
# foo = []
# expect(foo).to be_empty
# end
# end
#
class NoLet < Cop
MSG = 'Avoid using `%<method>s` ' \
'– use a method call or local variable instead.'

def_node_matcher :let?, <<~PATTERN
(send nil? :let ...)
PATTERN

def_node_matcher :subject?, <<~PATTERN
(send nil? :subject ...)
PATTERN

def on_send(node)
if subject?(node) && !allow_subject?
add_offense(node, message: format(MSG, method: 'subject'))
elsif let?(node)
add_offense(node, message: format(MSG, method: 'let'))
end
end

private

def allow_subject?
cop_config['AllowSubject']
end
end
end
end
end
1 change: 1 addition & 0 deletions lib/rubocop/cop/rspec_cops.rb
Expand Up @@ -66,6 +66,7 @@
require_relative 'rspec/multiple_subjects'
require_relative 'rspec/named_subject'
require_relative 'rspec/nested_groups'
require_relative 'rspec/no_let'
require_relative 'rspec/not_to_not'
require_relative 'rspec/overwriting_setup'
require_relative 'rspec/pending'
Expand Down
1 change: 1 addition & 0 deletions manual/cops.md
Expand Up @@ -65,6 +65,7 @@
* [RSpec/MultipleSubjects](cops_rspec.md#rspecmultiplesubjects)
* [RSpec/NamedSubject](cops_rspec.md#rspecnamedsubject)
* [RSpec/NestedGroups](cops_rspec.md#rspecnestedgroups)
* [RSpec/NoLet](cops_rspec.md#rspecnolet)
* [RSpec/NotToNot](cops_rspec.md#rspecnottonot)
* [RSpec/OverwritingSetup](cops_rspec.md#rspecoverwritingsetup)
* [RSpec/Pending](cops_rspec.md#rspecpending)
Expand Down
71 changes: 71 additions & 0 deletions manual/cops_rspec.md
Expand Up @@ -2263,6 +2263,77 @@ Max | `3` | Integer

* [https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/NestedGroups](https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/NestedGroups)

## RSpec/NoLet

Enabled by default | Supports autocorrection
--- | ---
Disabled | No

Checks for usage of `let` blocks in specs.

This cop can be configured with the option `AllowSubject` which
will configure the cop to only register offenses on explicit calls to
`let` and not calls to `subject`

### Examples

```ruby
# bad
describe MyClass do
let(:foo) { [] }
it { expect(foo).to be_empty }
end

describe MyClass do
subject(:foo) { [] }
it { expect(foo).to be_empty }
end

# good
describe MyClass do
it do
foo = []
expect(foo).to be_empty
end
end
```
#### with AllowSubject configuration

```ruby
# rubocop.yml
# RSpec/NoLet:
# AllowSubject: true

# bad
describe MyClass do
let(:foo) { [] }
it { expect(foo).to be_empty }
end

# good
describe MyClass do
subject(:foo) { [] }
it { expect(foo).to be_empty }
end

describe MyClass do
it do
foo = []
expect(foo).to be_empty
end
end
```

### Configurable attributes

Name | Default value | Configurable values
--- | --- | ---
AllowSubject | `false` | Boolean

### References

* [https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/NoLet](https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/NoLet)

## RSpec/NotToNot

Enabled by default | Supports autocorrection
Expand Down
64 changes: 64 additions & 0 deletions spec/rubocop/cop/rspec/no_let_spec.rb
@@ -0,0 +1,64 @@
# frozen_string_literal: true

RSpec.describe RuboCop::Cop::RSpec::NoLet do
subject(:cop) { described_class.new(config) }

let(:config) { RuboCop::Config.new }

# TODO: Write test code
#
# For example
it 'flags an offense when using `#let`' do
expect_offense(<<~RUBY)
let(:foo) { Foo.new }
^^^^^^^^^ Avoid using `let` – use a method call or local variable instead.
RUBY
end

it 'flags an offense when using `#subject` without name' do
expect_offense(<<~RUBY)
subject { Foo.new }
^^^^^^^ Avoid using `subject` – use a method call or local variable instead.
RUBY
end

it 'flags an offense when using `#subject` with name' do
expect_offense(<<~RUBY)
subject(:foo) { Foo.new }
^^^^^^^^^^^^^ Avoid using `subject` – use a method call or local variable instead.
RUBY
end

it 'does not flag an offense when using `#before`' do
expect_no_offenses(<<~RUBY)
before { foo }
RUBY
end

context 'when using AllowSubject configuration', :config do
subject(:cop) { described_class.new(config) }

let(:cop_config) do
{ 'AllowSubject' => true }
end

it 'flags an offense when using `#let`' do
expect_offense(<<~RUBY)
let(:foo) { Foo.new }
^^^^^^^^^ Avoid using `let` – use a method call or local variable instead.
RUBY
end

it 'does not flag an offense when using `#subject` without a name' do
expect_no_offenses(<<~RUBY)
subject { Foo.new }
RUBY
end

it 'does not flag an offense when using `#subject` with a name' do
expect_no_offenses(<<~RUBY)
subject(:foo) { Foo.new }
RUBY
end
end
end

0 comments on commit 5d70d0b

Please sign in to comment.