Skip to content

Commit 18e318f

Browse files
authored
Merge pull request #8279 from Fatsoma/feature/require_order_block_pass
Detect require block passed as argument in Lint/NonDeterministicRequireOrder
2 parents 0c786c0 + fe066fc commit 18e318f

File tree

4 files changed

+151
-40
lines changed

4 files changed

+151
-40
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
### New features
66

7+
* [#8279](https://github.com/rubocop-hq/rubocop/pull/8279): Recognise require method passed as argument in `Lint/NonDeterministicRequireOrder` cop. ([@biinari][])
78
* [#7333](https://github.com/rubocop-hq/rubocop/issues/7333): Add new `Style/RedundantFileExtensionInRequire` cop. ([@fatkodima][])
89
* [#8316](https://github.com/rubocop-hq/rubocop/pull/8316): Support autocorrect for `Lint/DisjunctiveAssignmentInConstructor` cop. ([@fatkodima][])
910
* [#8242](https://github.com/rubocop-hq/rubocop/pull/8242): Internal profiling available with `bin/rubocop-profile` and rake tasks. ([@marcandre][])

docs/modules/ROOT/pages/cops_lint.adoc

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1943,6 +1943,24 @@ Dir.glob(Rails.root.join(__dir__, 'test', '*.rb')).sort.each do |file|
19431943
end
19441944
----
19451945

1946+
[source,ruby]
1947+
----
1948+
# bad
1949+
Dir['./lib/**/*.rb'].each(&method(:require))
1950+
1951+
# good
1952+
Dir['./lib/**/*.rb'].sort.each(&method(:require))
1953+
----
1954+
1955+
[source,ruby]
1956+
----
1957+
# bad
1958+
Dir.glob(Rails.root.join('test', '*.rb'), &method(:require))
1959+
1960+
# good
1961+
Dir.glob(Rails.root.join('test', '*.rb')).sort.each(&method(:require))
1962+
----
1963+
19461964
== Lint/NonLocalExitFromIterator
19471965

19481966
|===

lib/rubocop/cop/lint/non_deterministic_require_order.rb

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,22 @@ module Lint
3535
# require file
3636
# end
3737
#
38+
# @example
39+
#
40+
# # bad
41+
# Dir['./lib/**/*.rb'].each(&method(:require))
42+
#
43+
# # good
44+
# Dir['./lib/**/*.rb'].sort.each(&method(:require))
45+
#
46+
# @example
47+
#
48+
# # bad
49+
# Dir.glob(Rails.root.join('test', '*.rb'), &method(:require))
50+
#
51+
# # good
52+
# Dir.glob(Rails.root.join('test', '*.rb')).sort.each(&method(:require))
53+
#
3854
class NonDeterministicRequireOrder < Cop
3955
MSG = 'Sort files before requiring them.'
4056

@@ -49,7 +65,16 @@ def on_block(node)
4965
end
5066
end
5167

68+
def on_block_pass(node)
69+
return unless method_require?(node)
70+
return unless unsorted_dir_pass?(node.parent)
71+
72+
add_offense(node.parent)
73+
end
74+
5275
def autocorrect(node)
76+
return correct_block_pass(node) if node.arguments.last&.block_pass_type?
77+
5378
if unsorted_dir_block?(node)
5479
lambda do |corrector|
5580
corrector.replace(node, "#{node.source}.sort.each")
@@ -64,10 +89,38 @@ def autocorrect(node)
6489

6590
private
6691

92+
def correct_block_pass(node)
93+
if unsorted_dir_glob_pass?(node)
94+
lambda do |corrector|
95+
block_arg = node.arguments.last
96+
corrector.remove(last_arg_range(node))
97+
corrector.insert_after(node, ".sort.each(#{block_arg.source})")
98+
end
99+
else
100+
lambda do |corrector|
101+
corrector.replace(node.loc.selector, 'sort.each')
102+
end
103+
end
104+
end
105+
106+
# Returns range of last argument including comma and whitespace.
107+
#
108+
# @return [Parser::Source::Range]
109+
#
110+
def last_arg_range(node)
111+
node.arguments.last.source_range.with(
112+
begin_pos: node.arguments[-2].source_range.end_pos
113+
)
114+
end
115+
67116
def unsorted_dir_loop?(node)
68117
unsorted_dir_block?(node) || unsorted_dir_each?(node)
69118
end
70119

120+
def unsorted_dir_pass?(node)
121+
unsorted_dir_glob_pass?(node) || unsorted_dir_each_pass?(node)
122+
end
123+
71124
def_node_matcher :unsorted_dir_block?, <<~PATTERN
72125
(send (const {nil? cbase} :Dir) :glob ...)
73126
PATTERN
@@ -76,6 +129,20 @@ def unsorted_dir_loop?(node)
76129
(send (send (const {nil? cbase} :Dir) {:[] :glob} ...) :each)
77130
PATTERN
78131

132+
def_node_matcher :method_require?, <<~PATTERN
133+
(block-pass (send nil? :method (sym :require)))
134+
PATTERN
135+
136+
def_node_matcher :unsorted_dir_glob_pass?, <<~PATTERN
137+
(send (const {nil? cbase} :Dir) :glob ...
138+
(block-pass (send nil? :method (sym :require))))
139+
PATTERN
140+
141+
def_node_matcher :unsorted_dir_each_pass?, <<~PATTERN
142+
(send (send (const {nil? cbase} :Dir) {:[] :glob} ...) :each
143+
(block-pass (send nil? :method (sym :require))))
144+
PATTERN
145+
79146
def_node_matcher :loop_variable, <<~PATTERN
80147
(args (arg $_))
81148
PATTERN

spec/rubocop/cop/lint/non_deterministic_require_order_spec.rb

Lines changed: 65 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -7,49 +7,54 @@
77

88
context 'when requiring files' do
99
context 'with unsorted index' do
10-
it 'registers an offsense' do
10+
it 'registers an offsense and autocorrects to add .sort' do
1111
expect_offense(<<~RUBY)
1212
Dir["./lib/**/*.rb"].each do |file|
1313
^^^^^^^^^^^^^^^^^^^^^^^^^ Sort files before requiring them.
1414
require file
1515
end
1616
RUBY
17+
expect_correction(<<~RUBY)
18+
Dir["./lib/**/*.rb"].sort.each do |file|
19+
require file
20+
end
21+
RUBY
1722
end
1823

1924
it 'registers an offsense with extra logic' do
2025
expect_offense(<<~RUBY)
2126
Dir["./lib/**/*.rb"].each do |file|
2227
^^^^^^^^^^^^^^^^^^^^^^^^^ Sort files before requiring them.
23-
if file.starts_with('_')
28+
if file.start_with?('_')
2429
puts "Not required."
2530
else
2631
require file
2732
end
2833
end
2934
RUBY
30-
end
31-
32-
it 'registers an offense with block passed as parameter' do
33-
pending
34-
expect_offense(<<~RUBY)
35-
Dir["./lib/**/*.rb"].each(&method(:require))
36-
^^^^^^^^^^^^^^^^^^^^^^^^^ Sort files before requiring them.
37-
RUBY
38-
end
39-
40-
it 'auto-corrects to add .sort' do
41-
new_source = autocorrect_source(<<~RUBY)
42-
Dir["./lib/**/*.rb"].each do |file|
43-
require file
44-
end
45-
RUBY
46-
expect(new_source).to eq(<<~RUBY)
35+
expect_correction(<<~RUBY)
4736
Dir["./lib/**/*.rb"].sort.each do |file|
48-
require file
37+
if file.start_with?('_')
38+
puts "Not required."
39+
else
40+
require file
41+
end
4942
end
5043
RUBY
5144
end
5245

46+
context 'with require block passed as parameter' do
47+
it 'registers an offense an autocorrects to add sort' do
48+
expect_offense(<<~RUBY)
49+
Dir["./lib/**/*.rb"].each(&method(:require))
50+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Sort files before requiring them.
51+
RUBY
52+
expect_correction(<<~RUBY)
53+
Dir["./lib/**/*.rb"].sort.each(&method(:require))
54+
RUBY
55+
end
56+
end
57+
5358
context 'with top-level ::Dir' do
5459
it 'registers an offense and corrects to add .sort' do
5560
expect_offense(<<~RUBY)
@@ -68,28 +73,32 @@
6873
end
6974

7075
context 'with unsorted glob' do
71-
it 'registers an offsense' do
76+
it 'registers an offsense and autocorrects to add .sort' do
7277
expect_offense(<<~RUBY)
7378
Dir.glob(Rails.root.join(__dir__, 'test', '*.rb'), File::FNM_DOTMATCH).each do |file|
7479
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Sort files before requiring them.
7580
require file
7681
end
7782
RUBY
78-
end
79-
80-
it 'auto-corrects to add .sort' do
81-
new_source = autocorrect_source(<<~RUBY)
82-
Dir.glob(Rails.root.join(__dir__, 'test', '*.rb'), File::FNM_DOTMATCH).each do |file|
83-
require file
84-
end
85-
RUBY
86-
expect(new_source).to eq(<<~RUBY)
83+
expect_correction(<<~RUBY)
8784
Dir.glob(Rails.root.join(__dir__, 'test', '*.rb'), File::FNM_DOTMATCH).sort.each do |file|
8885
require file
8986
end
9087
RUBY
9188
end
9289

90+
context 'with require block passed as parameter' do
91+
it 'registers an offense an autocorrects to add sort' do
92+
expect_offense(<<~RUBY)
93+
Dir.glob(Rails.root.join('test', '*.rb')).each(&method(:require))
94+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Sort files before requiring them.
95+
RUBY
96+
expect_correction(<<~RUBY)
97+
Dir.glob(Rails.root.join('test', '*.rb')).sort.each(&method(:require))
98+
RUBY
99+
end
100+
end
101+
93102
context 'with top-level ::Dir' do
94103
it 'registers an offense and corrects to add .sort' do
95104
expect_offense(<<~RUBY)
@@ -108,28 +117,39 @@
108117
end
109118

110119
context 'with direct block glob' do
111-
it 'registers an offsense' do
120+
it 'registers an offsense and autocorrects to add .sort.each' do
112121
expect_offense(<<~RUBY)
113122
Dir.glob("./lib/**/*.rb") do |file|
114123
^^^^^^^^^^^^^^^^^^^^^^^^^ Sort files before requiring them.
115124
require file
116125
end
117126
RUBY
118-
end
119-
120-
it 'auto-corrects to add .sort.each' do
121-
new_source = autocorrect_source(<<~RUBY)
122-
Dir.glob("./lib/**/*.rb") do |file|
123-
require file
124-
end
125-
RUBY
126-
expect(new_source).to eq(<<~RUBY)
127+
expect_correction(<<~RUBY)
127128
Dir.glob("./lib/**/*.rb").sort.each do |file|
128129
require file
129130
end
130131
RUBY
131132
end
132133

134+
context 'with require block passed as parameter' do
135+
it 'registers an offense and autocorrects to add sort' do
136+
expect_offense(<<~RUBY)
137+
Dir.glob(
138+
^^^^^^^^^ Sort files before requiring them.
139+
Rails.root.join('./lib/**/*.rb'),
140+
File::FNM_DOTMATCH,
141+
&method(:require)
142+
)
143+
RUBY
144+
expect_correction(<<~RUBY)
145+
Dir.glob(
146+
Rails.root.join('./lib/**/*.rb'),
147+
File::FNM_DOTMATCH
148+
).sort.each(&method(:require))
149+
RUBY
150+
end
151+
end
152+
133153
context 'with top-level ::Dir' do
134154
it 'registers an offense and corrects to add .sort.each' do
135155
expect_offense(<<~RUBY)
@@ -138,6 +158,11 @@
138158
require file
139159
end
140160
RUBY
161+
expect_correction(<<~RUBY)
162+
::Dir.glob("./lib/**/*.rb").sort.each do |file|
163+
require file
164+
end
165+
RUBY
141166
end
142167
end
143168
end

0 commit comments

Comments
 (0)