-
-
Notifications
You must be signed in to change notification settings - Fork 3.1k
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
Add 'Style/UnpackFirst' cop #5643
Conversation
db347b0
to
9664d49
Compare
lib/rubocop/cop/style/unpack.rb
Outdated
|
||
minimum_target_ruby_version 2.4 | ||
|
||
MSG = 'Use `unpack1(%<format>s)` instead of '\ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The message might be more informative with the receiver of unpack
in it.
The cop looks good overall. I wonder of the generic name |
b13f0a2
to
01e22fe
Compare
Thanks for the feedback. Agreed on both points and just pushed the changes 😄 |
There's one (not very commonly used) syntax that's unaccounted for. Accessing like so: foo.[](0) Not sure if it's worth accounting for this in the first version, unless it can be easily handled. 🙂 |
TIL! I'll look at adding at later today. |
@Drenmi Other cops like |
@Drenmi I added that syntax (and found some others 😄) but was not able to get the message completely right 🤔 |
It is a tough call between classifying as style vs performance some times. Like you said, this unlikely to be a performance bottleneck, but there is a clear performance gain in using [2] pry(main)> FOO_BAR = 'foo bar'.freeze
[5] pry(main)> Benchmark.ips do |x|
[5] pry(main)* x.report('unpack[0]') { FOO_BAR.unpack('Z*Z*')[0] }
[5] pry(main)* x.report('unpack1') { FOO_BAR.unpack1('Z*Z*') }
[5] pry(main)* x.compare!
[5] pry(main)* end
Warming up --------------------------------------
unpack[0] 169.518k i/100ms
unpack1 218.699k i/100ms
Calculating -------------------------------------
unpack[0] 3.356M (± 5.3%) i/s - 16.782M in 5.015638s
unpack1 5.533M (± 5.5%) i/s - 27.775M in 5.036752s
Comparison:
unpack1: 5532904.7 i/s
unpack[0]: 3355751.7 i/s - 1.65x slower |
MSG = 'Use `String#unpack1(%<format>s)` instead of '\ | ||
'`String#unpack(%<format>s)%<method>s`.'.freeze | ||
|
||
def_node_matcher :unpack_and_first_element?, <<-PATTERN |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thoughts for later: I wonder if we could capture this into a reusable form to make it easier for other cops to also identify that they are grabbing the first element from an array. Performance/Sample
also looks for grabbing the first element, but is missing checks for at
and take
.
@bdewater Here is a fix for the messaging, https://gist.github.com/rrosenblum/240d8343c4aa7377a73f5170ddbaf6c3. |
215a9ec
to
3e3cd67
Compare
@rrosenblum thank you very much! 🙇🏼♂️ Learned something new today 😄 I believe this is now ready to be merged. |
config/enabled.yml
Outdated
@@ -2010,6 +2010,12 @@ Style/UnneededPercentQ: | |||
StyleGuide: '#percent-q' | |||
Enabled: true | |||
|
|||
Style/UnpackFirst: | |||
Description: >- | |||
Checks for accessing the first element of unpack's result |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
unpack and unpack1 should be surrounded by back-ticks (`).
module RuboCop | ||
module Cop | ||
module Style | ||
# This cop checks for accessing the first element of *unpack* which can |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use back-ticks instead of asterisks around unpack and unpack1.
|
||
def autocorrect(node) | ||
lambda do |corrector| | ||
corrector.remove(node.loc.dot) if node.method_name == :first |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There are no tests for the auto-correction of take
, slice
, and at
. I think the auto-correction is going to produce incorrect code when one of those is used.
400b052
to
2caa223
Compare
|
||
minimum_target_ruby_version 2.4 | ||
|
||
MSG = 'Use `String#unpack1(%<format>s)` instead of '\ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not <%format>s.unpack1
? That reads better to me. Same for the other part of the message.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Because format
is the argument to unpack(1)
, not the string to be unpacked.
I'm happy to leave the String
prefix off, but I thought that was the suggestion you made in your earlier comment.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I meant I wanted the message to say something like Use
foo.unpack1instead of
foo...`. When using the actual code that you're checking in the messages they are easier to understand.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, I updated the message to show this.
This is a shorter way of getting the data you will need most of the time when unpacking a string. While unpack1 is also faster (it skips creating the intermediate array object), I consider it to be more style related like Style/SymbolProc. The bad form runs 3+ million iterations per second, which is not a performance bottleneck for most Ruby apps.
Guys, so sorry for the cross-reference spam above. Only realised it was happening today (was due to a change we made last week) and fixed it immediately in dependabot/dependabot-core@2bd343b. Unfortunately there's no way for us to delete the old cross-references, so they'll stay there as a reminder of our mistake 😞 |
@greysteil No worries! |
This is a shorter way of getting the data you will need most of the time when unpacking a string. It was introduced in Ruby 2.4: https://ruby-doc.org/core-2.4.0/String.html#method-i-unpack1
While unpack1 is also faster (it skips creating the intermediate array object), I consider it to be more style related like Style/SymbolProc. The bad form runs 3+ million iterations per second on my laptop, which is not a performance bottleneck for most Ruby apps.
Before submitting the PR make sure the following are checked:
master
(if not - rebase it).and description in grammatically correct, complete sentences.
rake default
orrake parallel
. It executes all tests and RuboCop for itself, and generates the documentation.