-
-
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 new Lint/NonDeterministicRequireOrder cop #7528
Add new Lint/NonDeterministicRequireOrder cop #7528
Conversation
f2fdf25
to
02586f9
Compare
02586f9
to
607379a
Compare
607379a
to
f0b033c
Compare
@buehmann What needs to happen before we can merge this? |
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.
@Mangara I just did a full review and left some comments. In the end we need someone from the core team to visit this.
context 'with unsorted glob' do | ||
it 'registers an offsense' do | ||
expect_offense(<<~RUBY) | ||
Dir.glob(Rails.root.join(__dir__, 'test', '*.rb'), File::FNM_DOTMATCH).each do |file| |
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.
Regarding your question
Should it test for more patterns?
I think it might make sense to support this case as well from the beginning:
Dir.glob(Rails.root.join(__dir__, 'test', '*.rb'), File::FNM_DOTMATCH) do |file|
require file
end
(that is glob
taking a block without each
)
What do you think? (Adding this should be rather easy with a {(send …) (send …)}
alternative in unsorted_dir_loop?
.)
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.
Yes! Good catch. We should definitely support that.
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.
Is this the best way to write the new pattern?
{ (send (const nil? :Dir) :glob ...) (send (send (const nil? :Dir) {:[] :glob} ...) :each) }
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.
(Only the .glob
variant supports the direct block syntax)
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 ended up splitting it into two patterns to help with autocorrection.
f0b033c
to
6a73177
Compare
var_name = loop_variable(node.arguments) | ||
|
||
return unless var_name && var_is_required?(node.body, var_name) | ||
|
||
add_offense(node.send_node) |
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 generated methods also take a block, which is yielded to in the case of a match. 🙂
loop_variable(node.arguments) do |var_name|
return unless var_is_required?(node.body, var_name)
add_offense(node.send_node)
end
Looks like you could also potentially combine the patterns into a single matcher.
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.
Ah, nice! I like the block syntax.
As for the single matcher, I like that all of the building blocks are fairly simple patterns. Merging them into one would make it harder to understand, I think.
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.
Great job! 🎉
Some minor nits, but overall looks good to me.
6a73177
to
0635f79
Compare
Can you rebase this on top of |
0635f79
to
161781d
Compare
The following commit is due to the impact of this cop addition. Can you squash your commits into one? |
And I think @bbatsov will decide whether the next release will be new feature release or bug fix release. Please wait until the next release is a release that includes new features. |
Dir[...] and Dir.glob(...) make no guarantees about the order files are returned in: > Case sensitivity depends on your system [...], as does the order > in which the results are returned. This becomes a problem when they are used for applications in which the order can matter, such as requiring files. At worst, this can lead to bugs that only happen intermittently in production and can't be reproduced in development. This cop suggests adding a .sort when requiring the files: > # bad > Dir["./lib/middleware/*.rb"].each do |file| > require file > end > # good > Dir["./lib/middleware/*.rb"].sort.each do |file| > require file > end
161781d
to
4e236c4
Compare
What is this PR trying to do?
Dir[...]
andDir.glob(...)
make no guarantees about the order files are returned in:This becomes a problem when they are used for applications in which the order can matter, such as requiring files. At worst, this can lead to bugs that only happen intermittently in production and can't be reproduced in development.
This cop suggests adding a
.sort
:Questions for reviewers
The current implementation very specifically looks for these three patterns:
Dir[_].each do |_|
,Dir.glob(_).each do |_|
, andDir.glob(_) do |_|
. Are there other common patterns that have the same effect? Should we allow for intermediate operations between theDir[...]
and.each
?require
d?There are other use cases where the order matters, although this cannot in general be detected statically, and you may want to avoid the performance penalty associated with sorting. We could add a configuration option:
Always
,OnlyForRequires
?Before submitting the PR make sure the following are checked:
[Fix #issue-number]
(if the related issue exists).master
(if not - rebase it).bundle exec rake default
. It executes all tests and RuboCop for itself, and generates the documentation.