Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix RSpec/SortMetadata cop to sort strings and variables first #1948

Open
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

cbliard
Copy link
Contributor

@cbliard cbliard commented Aug 12, 2024

Fixes #1946.

Symbols in metadata are processed by RSpec only when they are positioned last, meaning the other parameter types must be positioned before the symbols. RSpec context/describe accepts second non-symbol argument as an additional description which is why strings are sorted first.

Questions for reviewers:

  • is it ok to sort strings before variables? I have the feeling that this cop's responsibility should be to just take care of sorting symbols at the end of the arguments list.
  • the message "Sort metadata alphabetically." does not fully reflect what the cop is doing anymore, and can be confusing for code like describe 'Something', :a, b, :c because the metadata is sorted. I could not come up with a good message. Suggestions?
  • I noticed an autocorrect issue with describe 'Something', :b, :a, { foo: :bar }: it gets autocorrected to describe 'Something', :b, :a, foo: :bar }. Should it be fixed in the same PR?
  • I updated the RuboCop::Cop::RSpec::Metadata#on_metadata_arguments method because it was skipping the last argument if it was not a hash. I also renamed symbols to metadata_arguments (or args in RSpec/SortMetadata cop). Should symbols be renamed to metadata_arguments or args in other cops relying on on_metadata too?

Before submitting the PR make sure the following are checked:

  • Feature branch is up-to-date with master (if not - rebase it).
  • Squashed related commits together.
  • Added tests.
  • Updated documentation.
  • Added an entry to the CHANGELOG.md if the new code introduces user-observable changes.
  • The build (bundle exec rake) passes (be sure to run this locally, since it may produce updated documentation that you will need to commit).

@cbliard cbliard requested a review from a team as a code owner August 12, 2024 06:37
it 'registers an offense when a symbol metadata is before second docstring ' \
'argument' do
expect_offense(<<~RUBY)
RSpec.describe 'Something', :a, :z, 'second docstring' do
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Invalid RSpec syntax:

ArgumentError:
  wrong number of arguments (given 4, expected 0..2)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes indeed.

Actually there was already an existing test on master which would lead to this ArgumentError, even after autocorrect:

it 'registers an offense when using mixed metadata ' \
'and both symbols metadata and hash keys are not in alphabetical order ' \
'and the hash values are complex objects' do
expect_offense(<<~RUBY)
it 'Something', variable, 'B', :a, key => {}, foo: ->(x) { bar(x) }, Identifier.sample => true, baz: Snafu.new do
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Sort metadata alphabetically.
end
RUBY
expect_correction(<<~RUBY)
it 'Something', :a, 'B', variable, baz: Snafu.new, foo: ->(x) { bar(x) }, Identifier.sample => true, key => {} do
end
RUBY
end

That's why I thought adding this test would be ok. Note that after autocorrect the last string argument is moved at the 2nd place, so the ArgumentError would not occur anymore.

How should I rewrite this one?

it 'registers an offense when a symbol metadata is before a variable ' \
'argument' do
expect_offense(<<~RUBY)
RSpec.describe 'Something', :a, :z, variable, foo: :bar do
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the variable holds something that is not a symbol, this will fail with a similar ArgumentError

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes indeed.

It is a bit like this existing test of MetadataStyle cop:

context 'with symbol metadata with another existing non-literal metadata' do
it 'registers offense' do
expect_offense(<<~RUBY)
describe 'Something', :a, b do
^^ Use hash style for metadata.
end
RUBY
expect_no_corrections
end
end

This one too will fail if b is not a symbol nor a hash.

How should I rewrite it then? Adding a variable = :v on the first line?

it 'does not register an offense when a symbol metadata is before a ' \
'variable argument being the last argument as it could be a hash' do
expect_no_offenses(<<~RUBY)
RSpec.describe 'Something', :a, :z, some_hash do
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What if it's a

RSpec.describe 'Something', :z, :a, some_hash do

?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Then it would register an offense and would autocorrect it like this:

RSpec.describe 'Something', :a, :z, some_hash do

This test was inspired from this MetadataStyle cop test:

context 'with symbol metadata with another existing non-literal metadata' do
it 'registers offense' do
expect_offense(<<~RUBY)
describe 'Something', :a, b do
^^ Use hash style for metadata.
end
RUBY
expect_no_corrections
end
end

It ensures that a variable is not moved at the start of the arguments list if this variable is at the end of the arguments list, because it could hold a hash.

Should I update the spec to swap :a and :z and expect the offense and correction as described above?

it 'does not register an offense when using a second level description ' \
'not in alphabetical order with symbol metadata' do
expect_no_offenses(<<~RUBY)
RSpec.describe 'Something', 'second docstring', :a, :b do
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Invalid syntax

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, this one is correct.

It's when there are more than 2 non-symbol arguments that the ArgumentError: wrong number of arguments (given 4, expected 0..2) is raised.

@pirj
Copy link
Member

pirj commented Aug 17, 2024

I suggest extending the spec in accordance with our findings. I'm happy to help on this front. Please accept my apologies for the delay in review, it's a summer season over here.

Copy link
Contributor Author

@cbliard cbliard left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I replied to your comments and I am open to amend this pull request with your suggestions.

Please accept my apologies for the delay as well. As you wrote, it's summer season.

it 'registers an offense when a symbol metadata is before second docstring ' \
'argument' do
expect_offense(<<~RUBY)
RSpec.describe 'Something', :a, :z, 'second docstring' do
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes indeed.

Actually there was already an existing test on master which would lead to this ArgumentError, even after autocorrect:

it 'registers an offense when using mixed metadata ' \
'and both symbols metadata and hash keys are not in alphabetical order ' \
'and the hash values are complex objects' do
expect_offense(<<~RUBY)
it 'Something', variable, 'B', :a, key => {}, foo: ->(x) { bar(x) }, Identifier.sample => true, baz: Snafu.new do
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Sort metadata alphabetically.
end
RUBY
expect_correction(<<~RUBY)
it 'Something', :a, 'B', variable, baz: Snafu.new, foo: ->(x) { bar(x) }, Identifier.sample => true, key => {} do
end
RUBY
end

That's why I thought adding this test would be ok. Note that after autocorrect the last string argument is moved at the 2nd place, so the ArgumentError would not occur anymore.

How should I rewrite this one?

it 'registers an offense when a symbol metadata is before a variable ' \
'argument' do
expect_offense(<<~RUBY)
RSpec.describe 'Something', :a, :z, variable, foo: :bar do
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes indeed.

It is a bit like this existing test of MetadataStyle cop:

context 'with symbol metadata with another existing non-literal metadata' do
it 'registers offense' do
expect_offense(<<~RUBY)
describe 'Something', :a, b do
^^ Use hash style for metadata.
end
RUBY
expect_no_corrections
end
end

This one too will fail if b is not a symbol nor a hash.

How should I rewrite it then? Adding a variable = :v on the first line?

it 'does not register an offense when a symbol metadata is before a ' \
'variable argument being the last argument as it could be a hash' do
expect_no_offenses(<<~RUBY)
RSpec.describe 'Something', :a, :z, some_hash do
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Then it would register an offense and would autocorrect it like this:

RSpec.describe 'Something', :a, :z, some_hash do

This test was inspired from this MetadataStyle cop test:

context 'with symbol metadata with another existing non-literal metadata' do
it 'registers offense' do
expect_offense(<<~RUBY)
describe 'Something', :a, b do
^^ Use hash style for metadata.
end
RUBY
expect_no_corrections
end
end

It ensures that a variable is not moved at the start of the arguments list if this variable is at the end of the arguments list, because it could hold a hash.

Should I update the spec to swap :a and :z and expect the offense and correction as described above?

it 'does not register an offense when using a second level description ' \
'not in alphabetical order with symbol metadata' do
expect_no_offenses(<<~RUBY)
RSpec.describe 'Something', 'second docstring', :a, :b do
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, this one is correct.

It's when there are more than 2 non-symbol arguments that the ArgumentError: wrong number of arguments (given 4, expected 0..2) is raised.

Fixes rubocop#1946.

Symbols in metadata are processed by RSpec only when they are positioned
last, meaning the other parameter types must be positioned before the
symbols. RSpec `context`/`describe` accepts second non-symbol argument
as an additional description which is why strings are sorted first.
@cbliard cbliard force-pushed the sort_metadata_symbols_at_the_end_of_args_list branch from 95de9d6 to 7d1c8b8 Compare September 11, 2024 06:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

RSpec/SortMetadata autocorrect can break context groups having additional description
2 participants