Skip to content

Commit

Permalink
Merge pull request #1378 from rubocop/bquorning.sort-tags
Browse files Browse the repository at this point in the history
Add new `RSpec/SortMetadata` cop
  • Loading branch information
bquorning authored Oct 21, 2022
2 parents fb2ccea + bc43dd6 commit 45d1882
Show file tree
Hide file tree
Showing 8 changed files with 368 additions and 0 deletions.
2 changes: 2 additions & 0 deletions .rubocop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,8 @@ RSpec/IdenticalEqualityAssertion:
Enabled: true
RSpec/NoExpectationExample:
Enabled: true
RSpec/SortMetadata:
Enabled: true
RSpec/SubjectDeclaration:
Enabled: true
RSpec/VerifiedDoubleReference:
Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
* Add new `RSpec/Capybara/SpecificActions` cop. ([@ydah][])
* Update `config/default.yml` removing deprecated option to make the config correctable by users. ([@ignaciovillaverde][])
* Do not attempt to auto-correct example groups with `include_examples` in `RSpec/LetBeforeExamples`. ([@pirj][])
* Add new `RSpec/SortMetadata` cop. ([@leoarnold][])

## 2.13.2 (2022-09-23)

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

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

RSpec/StubbedMock:
Description: Checks that message expectations do not have a configured response.
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 @@ -80,6 +80,7 @@
* xref:cops_rspec.adoc#rspecsharedcontext[RSpec/SharedContext]
* xref:cops_rspec.adoc#rspecsharedexamples[RSpec/SharedExamples]
* xref:cops_rspec.adoc#rspecsingleargumentmessagechain[RSpec/SingleArgumentMessageChain]
* xref:cops_rspec.adoc#rspecsortmetadata[RSpec/SortMetadata]
* xref:cops_rspec.adoc#rspecstubbedmock[RSpec/StubbedMock]
* xref:cops_rspec.adoc#rspecsubjectdeclaration[RSpec/SubjectDeclaration]
* xref:cops_rspec.adoc#rspecsubjectstub[RSpec/SubjectStub]
Expand Down
33 changes: 33 additions & 0 deletions docs/modules/ROOT/pages/cops_rspec.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -4464,6 +4464,39 @@ allow(foo).to receive("bar.baz")

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

== RSpec/SortMetadata

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

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

Sort RSpec metadata alphabetically.

=== Examples

[source,ruby]
----
# bad
describe 'Something', :b, :a
context 'Something', foo: 'bar', baz: true
it 'works', :b, :a, foo: 'bar', baz: true
# good
describe 'Something', :a, :b
context 'Something', baz: true, foo: 'bar'
it 'works', :a, :b, baz: true, foo: 'bar'
----

=== References

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

== RSpec/StubbedMock

|===
Expand Down
102 changes: 102 additions & 0 deletions lib/rubocop/cop/rspec/sort_metadata.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
# frozen_string_literal: true

module RuboCop
module Cop
module RSpec
# Sort RSpec metadata alphabetically.
#
# @example
# # bad
# describe 'Something', :b, :a
# context 'Something', foo: 'bar', baz: true
# it 'works', :b, :a, foo: 'bar', baz: true
#
# # good
# describe 'Something', :a, :b
# context 'Something', baz: true, foo: 'bar'
# it 'works', :a, :b, baz: true, foo: 'bar'
#
class SortMetadata < Base
extend AutoCorrector
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 #Includes.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)
return if sorted?(symbols, pairs)

crime_scene = crime_scene(symbols, pairs)
add_offense(crime_scene) do |corrector|
corrector.replace(crime_scene, replacement(symbols, pairs))
end
end

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

range_between(
metadata.first.loc.expression.begin_pos,
metadata.last.loc.expression.end_pos
)
end

def replacement(symbols, pairs)
(sort_symbols(symbols) + sort_pairs(pairs)).map(&:source).join(', ')
end

def sorted?(symbols, pairs)
symbols == sort_symbols(symbols) && pairs == sort_pairs(pairs)
end

def sort_pairs(pairs)
pairs.sort_by { |pair| pair.key.source.downcase }
end

def sort_symbols(symbols)
symbols.sort_by do |symbol|
if %i[str sym].include?(symbol.type)
symbol.value.to_s.downcase
else
symbol.source.downcase
end
end
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 @@ -101,6 +101,7 @@
require_relative 'rspec/shared_context'
require_relative 'rspec/shared_examples'
require_relative 'rspec/single_argument_message_chain'
require_relative 'rspec/sort_metadata'
require_relative 'rspec/stubbed_mock'
require_relative 'rspec/subject_declaration'
require_relative 'rspec/subject_stub'
Expand Down
Loading

0 comments on commit 45d1882

Please sign in to comment.