Skip to content

Commit

Permalink
Add NoLet cop
Browse files Browse the repository at this point in the history
  • Loading branch information
mockdeep committed Jun 14, 2020
1 parent b327884 commit 2b60c75
Show file tree
Hide file tree
Showing 7 changed files with 230 additions and 0 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
* Add `RSpec/RepeatedExampleGroupDescription` cop. ([@lazycoder9][])
* Add block name and other lines to `RSpec/ScatteredSetup` message. ([@elebow][])
* Fix `RSpec/RepeatedDescription` to take into account example metadata. ([@lazycoder9][])
* Add `RSpec/NoLet` cop. ([@mockdeep][])

## 1.37.1 (2019-12-16)

Expand Down Expand Up @@ -520,3 +521,4 @@ Compatibility release so users can upgrade RuboCop to 0.51.0. No new features.
[@robotdana]: https://github.com/robotdana
[@rolfschmidt]: https://github.com/rolfschmidt
[@andrykonchin]: https://github.com/andrykonchin
[@mockdeep]: https://github.com/mockdeep
6 changes: 6 additions & 0 deletions config/default.yml
Original file line number Diff line number Diff line change
Expand Up @@ -415,6 +415,12 @@ RSpec/NestedGroups:
VersionChanged: '1.10'
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.
Enabled: true
Expand Down
85 changes: 85 additions & 0 deletions lib/rubocop/cop/rspec/no_let.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
# frozen_string_literal: true

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
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,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
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,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
Original file line number Diff line number Diff line change
Expand Up @@ -2322,6 +2322,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 | Safe | Supports autocorrection | VersionAdded | VersionChanged
Expand Down
64 changes: 64 additions & 0 deletions spec/rubocop/cop/rspec/no_let_spec.rb
Original file line number Diff line number Diff line change
@@ -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 2b60c75

Please sign in to comment.