forked from rubocop/rubocop
/
non_deterministic_require_order.rb
89 lines (78 loc) · 2.42 KB
/
non_deterministic_require_order.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
# frozen_string_literal: true
module RuboCop
module Cop
module Lint
# `Dir[...]` and `Dir.glob(...)` do not make any guarantees about
# the order in which files are returned. The final order is
# determined by the operating system and file system.
# This means that using them in cases where the order matters,
# such as requiring files, can lead to intermittent failures
# that are hard to debug. To ensure this doesn't happen,
# always sort the list.
#
# @example
#
# # bad
# Dir["./lib/**/*.rb"].each do |file|
# require file
# end
#
# # good
# Dir["./lib/**/*.rb"].sort.each do |file|
# require file
# end
#
# @example
#
# # bad
# Dir.glob(Rails.root.join(__dir__, 'test', '*.rb')) do |file|
# require file
# end
#
# # good
# Dir.glob(Rails.root.join(__dir__, 'test', '*.rb')).sort.each do |file|
# require file
# end
#
class NonDeterministicRequireOrder < Cop
MSG = 'Sort files before requiring them.'
def on_block(node)
return unless node.body
return unless unsorted_dir_loop?(node.send_node)
loop_variable(node.arguments) do |var_name|
return unless var_is_required?(node.body, var_name)
add_offense(node.send_node)
end
end
def autocorrect(node)
if unsorted_dir_block?(node)
lambda do |corrector|
corrector.replace(node, "#{node.source}.sort.each")
end
else
lambda do |corrector|
source = node.receiver.source
corrector.replace(node, "#{source}.sort.each")
end
end
end
private
def unsorted_dir_loop?(node)
unsorted_dir_block?(node) || unsorted_dir_each?(node)
end
def_node_matcher :unsorted_dir_block?, <<~PATTERN
(send (const nil? :Dir) :glob ...)
PATTERN
def_node_matcher :unsorted_dir_each?, <<~PATTERN
(send (send (const nil? :Dir) {:[] :glob} ...) :each)
PATTERN
def_node_matcher :loop_variable, <<~PATTERN
(args (arg $_))
PATTERN
def_node_search :var_is_required?, <<~PATTERN
(send nil? :require (lvar %1))
PATTERN
end
end
end
end