Skip to content

Commit

Permalink
Add RSpec/DuplicatedMetadata cop
Browse files Browse the repository at this point in the history
  • Loading branch information
r7kamura committed Nov 15, 2022
1 parent 2a83e3c commit f6032f5
Show file tree
Hide file tree
Showing 11 changed files with 252 additions and 35 deletions.
2 changes: 2 additions & 0 deletions .rubocop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,8 @@ RSpec/ChangeByZero:
Enabled: true
RSpec/ClassCheck:
Enabled: true
RSpec/DuplicatedMetadata:
Enabled: true
RSpec/ExcessiveDocstringSpacing:
Enabled: true
RSpec/IdenticalEqualityAssertion:
Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

- Add new `RSpec/FactoryBot/FactoryNameStyle` cop. ([@ydah])
- Fix wrong autocorrection in `n_times` style on `RSpec/FactoryBot/CreateList`. ([@r7kamura])
- Add `RSpec/DuplicatedMetadata` cop. ([@r7kamura])

## 2.15.0 (2022-11-03)

Expand Down
6 changes: 6 additions & 0 deletions config/default.yml
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,12 @@ RSpec/Dialect:
VersionAdded: '1.33'
Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/Dialect

RSpec/DuplicatedMetadata:
Description: Avoid duplicated metadata.
Enabled: pending
VersionAdded: "<<next>>"
Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/DuplicatedMetadata

RSpec/EmptyExampleGroup:
Description: Checks if an example group does not include any tests.
Enabled: true
Expand Down
1 change: 1 addition & 0 deletions docs/modules/ROOT/pages/cops.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
* xref:cops_rspec.adoc#rspecdescribedclass[RSpec/DescribedClass]
* xref:cops_rspec.adoc#rspecdescribedclassmodulewrapping[RSpec/DescribedClassModuleWrapping]
* xref:cops_rspec.adoc#rspecdialect[RSpec/Dialect]
* xref:cops_rspec.adoc#rspecduplicatedmetadata[RSpec/DuplicatedMetadata]
* xref:cops_rspec.adoc#rspecemptyexamplegroup[RSpec/EmptyExampleGroup]
* xref:cops_rspec.adoc#rspecemptyhook[RSpec/EmptyHook]
* xref:cops_rspec.adoc#rspecemptylineafterexample[RSpec/EmptyLineAfterExample]
Expand Down
29 changes: 29 additions & 0 deletions docs/modules/ROOT/pages/cops_rspec.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -1001,6 +1001,35 @@ end

* https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/Dialect

== RSpec/DuplicatedMetadata

|===
| Enabled by default | Safe | Supports autocorrection | Version Added | Version Changed

| Pending
| Yes
| Yes
| <<next>>
| -
|===

Avoid duplicated metadata.

=== Examples

[source,ruby]
----
# bad
describe 'Something', :a, :a
# good
describe 'Something', :a
----

=== References

* https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/DuplicatedMetadata

== RSpec/EmptyExampleGroup

|===
Expand Down
1 change: 1 addition & 0 deletions lib/rubocop-rspec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
require_relative 'rubocop/cop/rspec/mixin/css_selector'
require_relative 'rubocop/cop/rspec/mixin/skip_or_pending'
require_relative 'rubocop/cop/rspec/mixin/capybara_help'
require_relative 'rubocop/cop/rspec/mixin/metadata'

require_relative 'rubocop/rspec/concept'
require_relative 'rubocop/rspec/example_group'
Expand Down
58 changes: 58 additions & 0 deletions lib/rubocop/cop/rspec/duplicated_metadata.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# frozen_string_literal: true

module RuboCop
module Cop
module RSpec
# Avoid duplicated metadata.
#
# @example
# # bad
# describe 'Something', :a, :a
#
# # good
# describe 'Something', :a
class DuplicatedMetadata < Base
extend AutoCorrector

include Metadata
include RangeHelp

MSG = 'Avoid duplicated metadata.'

def on_metadata(symbols, _pairs)
symbols.each do |symbol|
on_metadata_symbol(symbol)
end
end

private

def on_metadata_symbol(node)
return unless duplicated?(node)

add_offense(node) do |corrector|
autocorrect(corrector, node)
end
end

def autocorrect(corrector, node)
corrector.remove(
range_with_surrounding_comma(
range_with_surrounding_space(
node.location.expression,
side: :left
),
:left
)
)
end

def duplicated?(node)
node.left_siblings.any? do |sibling|
sibling.eql?(node)
end
end
end
end
end
end
49 changes: 49 additions & 0 deletions lib/rubocop/cop/rspec/mixin/metadata.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# frozen_string_literal: true

module RuboCop
module Cop
module RSpec
# Helper methods to find RSpec metadata.
module Metadata
extend RuboCop::NodePattern::Macros

include RuboCop::RSpec::Language

# @!method rspec_metadata(node)
def_node_matcher :rspec_metadata, <<~PATTERN
(block
(send
#rspec? {#Examples.all #ExampleGroups.all #SharedGroups.all #Hooks.all} _ ${send str sym}* (hash $...)?)
...)
PATTERN

# @!method rspec_configure(node)
def_node_matcher :rspec_configure, <<~PATTERN
(block (send #rspec? :configure) (args (arg $_)) ...)
PATTERN

# @!method metadata_in_block(node)
def_node_search :metadata_in_block, <<~PATTERN
(send (lvar %) #Hooks.all _ ${send str sym}* (hash $...)?)
PATTERN

def on_block(node)
rspec_configure(node) do |block_var|
metadata_in_block(node, block_var) do |symbols, pairs|
on_metadata(symbols, pairs.flatten)
end
end

rspec_metadata(node) do |symbols, pairs|
on_metadata(symbols, pairs.flatten)
end
end
alias on_numblock on_block

def on_metadata(_symbols, _pairs)
raise ::NotImplementedError
end
end
end
end
end
39 changes: 4 additions & 35 deletions lib/rubocop/cop/rspec/sort_metadata.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,45 +18,12 @@ module RSpec
#
class SortMetadata < Base
extend AutoCorrector
include Metadata
include RangeHelp

MSG = 'Sort metadata alphabetically.'

# @!method rspec_metadata(node)
def_node_matcher :rspec_metadata, <<~PATTERN
(block
(send
#rspec? {#Examples.all #ExampleGroups.all #SharedGroups.all #Hooks.all} _ ${send str sym}* (hash $...)?)
...)
PATTERN

# @!method rspec_configure(node)
def_node_matcher :rspec_configure, <<~PATTERN
(block (send #rspec? :configure) (args (arg $_)) ...)
PATTERN

# @!method metadata_in_block(node)
def_node_search :metadata_in_block, <<~PATTERN
(send (lvar %) #Hooks.all _ ${send str sym}* (hash $...)?)
PATTERN

def on_block(node)
rspec_configure(node) do |block_var|
metadata_in_block(node, block_var) do |symbols, pairs|
investigate(symbols, pairs.flatten)
end
end

rspec_metadata(node) do |symbols, pairs|
investigate(symbols, pairs.flatten)
end
end

alias on_numblock on_block

private

def investigate(symbols, pairs)
def on_metadata(symbols, pairs)
return if sorted?(symbols, pairs)

crime_scene = crime_scene(symbols, pairs)
Expand All @@ -65,6 +32,8 @@ def investigate(symbols, pairs)
end
end

private

def crime_scene(symbols, pairs)
metadata = symbols + pairs

Expand Down
1 change: 1 addition & 0 deletions lib/rubocop/cop/rspec_cops.rb
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
require_relative 'rspec/described_class'
require_relative 'rspec/described_class_module_wrapping'
require_relative 'rspec/dialect'
require_relative 'rspec/duplicated_metadata'
require_relative 'rspec/empty_example_group'
require_relative 'rspec/empty_hook'
require_relative 'rspec/empty_line_after_example'
Expand Down
100 changes: 100 additions & 0 deletions spec/rubocop/cop/rspec/duplicated_metadata_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
# frozen_string_literal: true

RSpec.describe RuboCop::Cop::RSpec::DuplicatedMetadata do
context 'when metadata is not used' do
it 'registers no offense' do
expect_no_offenses(<<~RUBY)
describe 'Something' do
end
RUBY
end
end

context 'when metadata is not duplicated' do
it 'registers no offense' do
expect_no_offenses(<<~RUBY)
describe 'Something', :a, :b do
end
RUBY
end
end

context 'when metadata is duplicated on example group' do
it 'registers offense' do
expect_offense(<<~RUBY)
describe 'Something', :a, :a do
^^ Avoid duplicated metadata.
end
RUBY

expect_correction(<<~RUBY)
describe 'Something', :a do
end
RUBY
end
end

context 'when metadata is duplicated in different order' do
it 'registers offense' do
expect_offense(<<~RUBY)
describe 'Something', :a, :b, :a do
^^ Avoid duplicated metadata.
end
RUBY

expect_correction(<<~RUBY)
describe 'Something', :a, :b do
end
RUBY
end
end

context 'when metadata is duplicated on example' do
it 'registers offense' do
expect_offense(<<~RUBY)
it 'does something', :a, :a do
^^ Avoid duplicated metadata.
end
RUBY

expect_correction(<<~RUBY)
it 'does something', :a do
end
RUBY
end
end

context 'when metadata is duplicated on shared examples' do
it 'registers offense' do
expect_offense(<<~RUBY)
shared_examples 'something', :a, :a do
^^ Avoid duplicated metadata.
end
RUBY

expect_correction(<<~RUBY)
shared_examples 'something', :a do
end
RUBY
end
end

context 'when metadata is duplicated on configuration hook' do
it 'registers offense' do
expect_offense(<<~RUBY)
RSpec.configure do |configuration|
configuration.before(:each, :a, :a) do
^^ Avoid duplicated metadata.
end
end
RUBY

expect_correction(<<~RUBY)
RSpec.configure do |configuration|
configuration.before(:each, :a) do
end
end
RUBY
end
end
end

0 comments on commit f6032f5

Please sign in to comment.